Pārlūkot izejas kodu

feat: generates source names (#1725)

This is the first step in adding support for multiple
streams per endpoint. Each source(stream?) needs to have
an identifier. For now always generate the 0 index name
until the machinery for sending more than 1 stream is put
in place.

* send only MSID attribute

* add feature flag for source name signaling

* log a msg if source name signaling is enabled
dev1
Paweł Domas 3 gadus atpakaļ
vecāks
revīzija
3bdf8a6b8f
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam

+ 6
- 0
JitsiMeetJS.js Parādīt failu

21
 import getActiveAudioDevice from './modules/detection/ActiveDeviceDetector';
21
 import getActiveAudioDevice from './modules/detection/ActiveDeviceDetector';
22
 import * as DetectionEvents from './modules/detection/DetectionEvents';
22
 import * as DetectionEvents from './modules/detection/DetectionEvents';
23
 import TrackVADEmitter from './modules/detection/TrackVADEmitter';
23
 import TrackVADEmitter from './modules/detection/TrackVADEmitter';
24
+import FeatureFlags from './modules/flags/FeatureFlags';
24
 import ProxyConnectionService
25
 import ProxyConnectionService
25
     from './modules/proxyconnection/ProxyConnectionService';
26
     from './modules/proxyconnection/ProxyConnectionService';
26
 import recordingConstants from './modules/recording/recordingConstants';
27
 import recordingConstants from './modules/recording/recordingConstants';
146
         Settings.init(options.externalStorage);
147
         Settings.init(options.externalStorage);
147
         Statistics.init(options);
148
         Statistics.init(options);
148
 
149
 
150
+        // Configure the feature flags.
151
+        FeatureFlags.init({
152
+            sourceNameSignaling: options.sourceNameSignaling
153
+        });
154
+
149
         // Initialize global window.connectionTimes
155
         // Initialize global window.connectionTimes
150
         // FIXME do not use 'window'
156
         // FIXME do not use 'window'
151
         if (!window.connectionTimes) {
157
         if (!window.connectionTimes) {

+ 30
- 0
modules/flags/FeatureFlags.js Parādīt failu

1
+import { getLogger } from 'jitsi-meet-logger';
2
+
3
+const logger = getLogger('FeatureFlags');
4
+
5
+/**
6
+ * A global module for accessing information about different feature flags state.
7
+ */
8
+class FeatureFlags {
9
+    /**
10
+     * Configures the module.
11
+     *
12
+     * @param {boolean} flags.sourceNameSignaling - Enables source names in the signaling.
13
+     */
14
+    init(flags) {
15
+        this._sourceNameSignaling = Boolean(flags.sourceNameSignaling);
16
+
17
+        logger.info(`Source name signaling: ${this._sourceNameSignaling}`);
18
+    }
19
+
20
+    /**
21
+     * Checks if the source name signaling is enabled.
22
+     *
23
+     * @returns {boolean}
24
+     */
25
+    isSourceNameSignalingEnabled() {
26
+        return this._sourceNameSignaling;
27
+    }
28
+}
29
+
30
+export default new FeatureFlags();

+ 41
- 0
modules/sdp/LocalSdpMunger.js Parādīt failu

5
 import MediaDirection from '../../service/RTC/MediaDirection';
5
 import MediaDirection from '../../service/RTC/MediaDirection';
6
 import * as MediaType from '../../service/RTC/MediaType';
6
 import * as MediaType from '../../service/RTC/MediaType';
7
 import VideoType from '../../service/RTC/VideoType';
7
 import VideoType from '../../service/RTC/VideoType';
8
+import FeatureFlags from '../flags/FeatureFlags';
8
 
9
 
9
 import { SdpTransformWrap } from './SdpTransformUtil';
10
 import { SdpTransformWrap } from './SdpTransformUtil';
10
 
11
 
309
 
310
 
310
         if (audioMLine) {
311
         if (audioMLine) {
311
             this._transformMediaIdentifiers(audioMLine);
312
             this._transformMediaIdentifiers(audioMLine);
313
+            this._injectSourceNames(audioMLine);
312
         }
314
         }
313
 
315
 
314
         const videoMLine = transformer.selectMedia('video');
316
         const videoMLine = transformer.selectMedia('video');
315
 
317
 
316
         if (videoMLine) {
318
         if (videoMLine) {
317
             this._transformMediaIdentifiers(videoMLine);
319
             this._transformMediaIdentifiers(videoMLine);
320
+            this._injectSourceNames(videoMLine);
318
         }
321
         }
319
 
322
 
320
         return new RTCSessionDescription({
323
         return new RTCSessionDescription({
322
             sdp: transformer.toRawSDP()
325
             sdp: transformer.toRawSDP()
323
         });
326
         });
324
     }
327
     }
328
+
329
+    /**
330
+     * Injects source names. Source names are need to for multiple streams per endpoint support. The final plan is to
331
+     * use the "mid" attribute for source names, but because the SDP to Jingle conversion still operates in the Plan-B
332
+     * semantics (one source name per media), a custom "name" attribute is injected into SSRC lines..
333
+     *
334
+     * @param {MLineWrap} mediaSection - The media part (audio or video) of the session description which will be
335
+     * modified in place.
336
+     * @returns {void}
337
+     * @private
338
+     */
339
+    _injectSourceNames(mediaSection) {
340
+        if (!FeatureFlags.isSourceNameSignalingEnabled()) {
341
+            return;
342
+        }
343
+
344
+        const sources = [ ...new Set(mediaSection.mLine?.ssrcs?.map(s => s.id)) ];
345
+        const mediaType = mediaSection.mLine?.type;
346
+
347
+        if (!mediaType) {
348
+            throw new Error('_transformMediaIdentifiers - no media type in mediaSection');
349
+        }
350
+
351
+        for (const source of sources) {
352
+            const nameExists = mediaSection.ssrcs.find(ssrc => ssrc.id === source && ssrc.attribute === 'name');
353
+
354
+            if (!nameExists) {
355
+                const firstLetterOfMediaType = mediaType.substring(0, 1);
356
+
357
+                // Inject source names as a=ssrc:3124985624 name:endpointA-v0
358
+                mediaSection.ssrcs.push({
359
+                    id: source,
360
+                    attribute: 'name',
361
+                    value: `${this.localEndpointId}-${firstLetterOfMediaType}0`
362
+                });
363
+            }
364
+        }
365
+    }
325
 }
366
 }

+ 58
- 25
modules/sdp/LocalSdpMunger.spec.js Parādīt failu

2
 import * as transform from 'sdp-transform';
2
 import * as transform from 'sdp-transform';
3
 
3
 
4
 import { MockPeerConnection } from '../RTC/MockClasses';
4
 import { MockPeerConnection } from '../RTC/MockClasses';
5
+import FeatureFlags from '../flags/FeatureFlags';
5
 
6
 
6
 import LocalSdpMunger from './LocalSdpMunger';
7
 import LocalSdpMunger from './LocalSdpMunger';
7
 import { default as SampleSdpStrings } from './SampleSdpStrings.js';
8
 import { default as SampleSdpStrings } from './SampleSdpStrings.js';
25
     const localEndpointId = 'sRdpsdg';
26
     const localEndpointId = 'sRdpsdg';
26
 
27
 
27
     beforeEach(() => {
28
     beforeEach(() => {
29
+        FeatureFlags.init({ });
28
         localSdpMunger = new LocalSdpMunger(tpc, localEndpointId);
30
         localSdpMunger = new LocalSdpMunger(tpc, localEndpointId);
29
     });
31
     });
30
     describe('stripSsrcs', () => {
32
     describe('stripSsrcs', () => {
31
-        beforeEach(() => { }); // eslint-disable-line no-empty-function
32
         it('should strip ssrcs from an sdp with no msid', () => {
33
         it('should strip ssrcs from an sdp with no msid', () => {
33
             localSdpMunger.tpc.isP2P = false;
34
             localSdpMunger.tpc.isP2P = false;
34
 
35
 
46
             expect(videoSsrcs.length).toEqual(0);
47
             expect(videoSsrcs.length).toEqual(0);
47
         });
48
         });
48
 
49
 
49
-        it('should do nothing to an sdp with msid', () => {
50
-            const sdpStr = transform.write(SampleSdpStrings.simulcastSdp);
51
-            const desc = new RTCSessionDescription({
52
-                type: 'offer',
53
-                sdp: sdpStr
50
+        describe('should do nothing to an sdp with msid', () => {
51
+            let audioSsrcs, videoSsrcs;
52
+
53
+            const transformStreamIdentifiers = () => {
54
+                const sdpStr = transform.write(SampleSdpStrings.simulcastSdp);
55
+                const desc = new RTCSessionDescription({
56
+                    type: 'offer',
57
+                    sdp: sdpStr
58
+                });
59
+                const transformedDesc = localSdpMunger.transformStreamIdentifiers(desc);
60
+                const newSdp = transform.parse(transformedDesc.sdp);
61
+
62
+                audioSsrcs = getSsrcLines(newSdp, 'audio');
63
+                videoSsrcs = getSsrcLines(newSdp, 'video');
64
+            };
65
+
66
+            it('without source name signaling enabled (no injected source name)', () => {
67
+                transformStreamIdentifiers();
68
+
69
+                expect(audioSsrcs.length).toEqual(4);
70
+                expect(videoSsrcs.length).toEqual(6);
54
             });
71
             });
55
-            const transformedDesc = localSdpMunger.transformStreamIdentifiers(desc);
56
-            const newSdp = transform.parse(transformedDesc.sdp);
57
-            const audioSsrcs = getSsrcLines(newSdp, 'audio');
58
-            const videoSsrcs = getSsrcLines(newSdp, 'video');
72
+            it('with source name signaling enabled (injected source name)', () => {
73
+                FeatureFlags.init({ sourceNameSignaling: true });
74
+                transformStreamIdentifiers();
59
 
75
 
60
-            expect(audioSsrcs.length).toEqual(4);
61
-            expect(videoSsrcs.length).toEqual(6);
76
+                expect(audioSsrcs.length).toEqual(4 + 1 /* injected source name */);
77
+                expect(videoSsrcs.length).toEqual(6 + 3 /* injected source name into each ssrc */);
78
+            });
62
         });
79
         });
63
     });
80
     });
64
 
81
 
108
     const localEndpointId = 'sRdpsdg';
125
     const localEndpointId = 'sRdpsdg';
109
 
126
 
110
     beforeEach(() => {
127
     beforeEach(() => {
128
+        FeatureFlags.init({ });
111
         localSdpMunger = new LocalSdpMunger(tpc, localEndpointId);
129
         localSdpMunger = new LocalSdpMunger(tpc, localEndpointId);
112
     });
130
     });
113
     describe('stripSsrcs', () => {
131
     describe('stripSsrcs', () => {
114
-        beforeEach(() => { }); // eslint-disable-line no-empty-function
115
-        it('should not strip ssrcs from an sdp with no msid', () => {
116
-            localSdpMunger.tpc.isP2P = false;
132
+        describe('should not strip ssrcs from an sdp with no msid', () => {
133
+            let audioSsrcs, videoSsrcs;
117
 
134
 
118
-            const sdpStr = transform.write(SampleSdpStrings.recvOnlySdp);
119
-            const desc = new RTCSessionDescription({
120
-                type: 'offer',
121
-                sdp: sdpStr
135
+            const transformStreamIdentifiers = () => {
136
+                localSdpMunger.tpc.isP2P = false;
137
+
138
+                const sdpStr = transform.write(SampleSdpStrings.recvOnlySdp);
139
+                const desc = new RTCSessionDescription({
140
+                    type: 'offer',
141
+                    sdp: sdpStr
142
+                });
143
+                const transformedDesc = localSdpMunger.transformStreamIdentifiers(desc);
144
+                const newSdp = transform.parse(transformedDesc.sdp);
145
+
146
+                audioSsrcs = getSsrcLines(newSdp, 'audio');
147
+                videoSsrcs = getSsrcLines(newSdp, 'video');
148
+            };
149
+
150
+            it('without source name signaling', () => {
151
+                transformStreamIdentifiers();
152
+
153
+                expect(audioSsrcs.length).toEqual(1);
154
+                expect(videoSsrcs.length).toEqual(1);
122
             });
155
             });
123
-            const transformedDesc = localSdpMunger.transformStreamIdentifiers(desc);
124
-            const newSdp = transform.parse(transformedDesc.sdp);
125
-            const audioSsrcs = getSsrcLines(newSdp, 'audio');
126
-            const videoSsrcs = getSsrcLines(newSdp, 'video');
156
+            it('with source name signaling', () => {
157
+                FeatureFlags.init({ sourceNameSignaling: true });
158
+                transformStreamIdentifiers();
127
 
159
 
128
-            expect(audioSsrcs.length).toEqual(1);
129
-            expect(videoSsrcs.length).toEqual(1);
160
+                expect(audioSsrcs.length).toEqual(1 + 1 /* injected source name */);
161
+                expect(videoSsrcs.length).toEqual(1 + 1 /* injected source name */);
162
+            });
130
         });
163
         });
131
     });
164
     });
132
 });
165
 });

+ 10
- 17
modules/sdp/SDP.js Parādīt failu

2
 
2
 
3
 import MediaDirection from '../../service/RTC/MediaDirection';
3
 import MediaDirection from '../../service/RTC/MediaDirection';
4
 import browser from '../browser';
4
 import browser from '../browser';
5
+import FeatureFlags from '../flags/FeatureFlags';
5
 
6
 
6
 import SDPUtil from './SDPUtil';
7
 import SDPUtil from './SDPUtil';
7
 
8
 
203
                 const ssrcMap = SDPUtil.parseSSRC(this.media[i]);
204
                 const ssrcMap = SDPUtil.parseSSRC(this.media[i]);
204
 
205
 
205
                 for (const [ availableSsrc, ssrcParameters ] of ssrcMap) {
206
                 for (const [ availableSsrc, ssrcParameters ] of ssrcMap) {
207
+                    const sourceName = SDPUtil.parseSourceNameLine(ssrcParameters);
208
+
206
                     elem.c('source', {
209
                     elem.c('source', {
207
                         ssrc: availableSsrc,
210
                         ssrc: availableSsrc,
211
+                        name: FeatureFlags.isSourceNameSignalingEnabled() ? sourceName : undefined,
208
                         xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
212
                         xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
209
                     });
213
                     });
210
 
214
 
211
-                    ssrcParameters.forEach(ssrcSdpLine => {
212
-                        // get everything after first space
213
-                        const idx = ssrcSdpLine.indexOf(' ');
214
-                        const kv = ssrcSdpLine.substr(idx + 1);
215
+                    const msid = SDPUtil.parseMSIDAttribute(ssrcParameters);
215
 
216
 
217
+                    // eslint-disable-next-line max-depth
218
+                    if (msid) {
216
                         elem.c('parameter');
219
                         elem.c('parameter');
217
-                        if (kv.indexOf(':') === -1) {
218
-                            elem.attrs({ name: kv });
219
-                        } else {
220
-                            const name = kv.split(':', 2)[0];
221
-
222
-                            elem.attrs({ name });
223
-
224
-                            let v = kv.split(':', 2)[1];
225
-
226
-                            v = SDPUtil.filterSpecialChars(v);
227
-                            elem.attrs({ value: v });
228
-                        }
220
+                        elem.attrs({ name: 'msid' });
221
+                        elem.attrs({ value: msid });
229
                         elem.up();
222
                         elem.up();
230
-                    });
223
+                    }
231
 
224
 
232
                     elem.up();
225
                     elem.up();
233
                 }
226
                 }

+ 42
- 13
modules/sdp/SDP.spec.js Parādīt failu

1
 /* globals $ */
1
 /* globals $ */
2
 import { $iq } from 'strophe.js';
2
 import { $iq } from 'strophe.js';
3
 
3
 
4
+import FeatureFlags from '../flags/FeatureFlags';
5
+
4
 import SDP from './SDP';
6
 import SDP from './SDP';
5
 
7
 
6
 /**
8
 /**
11
 }
13
 }
12
 
14
 
13
 describe('SDP', () => {
15
 describe('SDP', () => {
16
+    afterEach(() => {
17
+        FeatureFlags.init({ });
18
+    });
14
     describe('toJingle', () => {
19
     describe('toJingle', () => {
15
         /* eslint-disable max-len*/
20
         /* eslint-disable max-len*/
16
         const testSdp = [
21
         const testSdp = [
35
             'a=fingerprint:sha-256 A9:00:CC:F9:81:33:EA:E9:E3:B4:01:E9:9E:18:B3:9B:F8:49:25:A0:5D:12:20:70:D5:6F:34:5A:2A:39:19:0A\r\n',
40
             'a=fingerprint:sha-256 A9:00:CC:F9:81:33:EA:E9:E3:B4:01:E9:9E:18:B3:9B:F8:49:25:A0:5D:12:20:70:D5:6F:34:5A:2A:39:19:0A\r\n',
36
             'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n',
41
             'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n',
37
             'a=ssrc:2002 cname:juejgy8a01\r\n',
42
             'a=ssrc:2002 cname:juejgy8a01\r\n',
43
+            'a=ssrc:2002 name:a8f7g30-a0\r\n',
38
             'a=rtcp-mux\r\n',
44
             'a=rtcp-mux\r\n',
39
             'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n',
45
             'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n',
40
             'c=IN IP4 0.0.0.0\r\n',
46
             'c=IN IP4 0.0.0.0\r\n',
65
             'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n',
71
             'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n',
66
             'a=ssrc:4004 cname:juejgy8a01\r\n',
72
             'a=ssrc:4004 cname:juejgy8a01\r\n',
67
             'a=ssrc:4005 cname:juejgy8a01\r\n',
73
             'a=ssrc:4005 cname:juejgy8a01\r\n',
74
+            'a=ssrc:4004 name:a8f7g30-v0\r\n',
75
+            'a=ssrc:4005 name:a8f7g30-v0\r\n',
68
             'a=ssrc-group:FID 4004 4005\r\n',
76
             'a=ssrc-group:FID 4004 4005\r\n',
69
             'a=rtcp-mux\r\n'
77
             'a=rtcp-mux\r\n'
70
         ].join('');
78
         ].join('');
87
             sdp.toJingle(accept, false);
95
             sdp.toJingle(accept, false);
88
 
96
 
89
             const { nodeTree } = accept;
97
             const { nodeTree } = accept;
90
-            const descriptions
91
-                = Array.from(nodeTree.getElementsByTagName('description'));
92
-            const videoDescriptions = descriptions.filter(description =>
93
-                description.getAttribute('media') === 'video');
94
-            const count = videoDescriptions.reduce((iterator, description) => {
95
-                const childNodes = Array.from(description.childNodes);
96
-                const childNodesSources = childNodes.filter(child =>
97
-                    child.nodeName === 'source');
98
-
99
-                return iterator + childNodesSources.length;
100
-            }, 0);
101
-
102
-            expect(count).toBe(2);
98
+            const videoSources = nodeTree.querySelectorAll('description[media=\'video\']>source');
99
+
100
+            expect(videoSources.length).toBe(2);
101
+        });
102
+        it('put source names as source element attributes', () => {
103
+            FeatureFlags.init({ sourceNameSignaling: true });
104
+
105
+            const sdp = new SDP(testSdp);
106
+            const accept = $iq({
107
+                to: 'peerjid',
108
+                type: 'set'
109
+            })
110
+                .c('jingle', {
111
+                    xmlns: 'urn:xmpp:jingle:1',
112
+                    action: 'session-accept',
113
+                    initiator: false,
114
+                    responder: true,
115
+                    sid: 'temp-sid'
116
+                });
117
+
118
+            sdp.toJingle(accept, false);
119
+
120
+            const { nodeTree } = accept;
121
+
122
+            const audioSources = nodeTree.querySelectorAll('description[media=\'audio\']>source');
123
+            const videoSources = nodeTree.querySelectorAll('description[media=\'video\']>source');
124
+
125
+            for (const source of audioSources) {
126
+                expect(source.getAttribute('name')).toBe('a8f7g30-a0');
127
+            }
128
+
129
+            for (const source of videoSources) {
130
+                expect(source.getAttribute('name')).toBe('a8f7g30-v0');
131
+            }
103
         });
132
         });
104
     });
133
     });
105
 
134
 

+ 16
- 17
modules/sdp/SDPDiffer.js Parādīt failu

1
+import FeatureFlags from '../flags/FeatureFlags';
2
+
1
 import SDPUtil from './SDPUtil';
3
 import SDPUtil from './SDPUtil';
2
 
4
 
3
 // this could be useful in Array.prototype.
5
 // this could be useful in Array.prototype.
167
         // generate sources from lines
169
         // generate sources from lines
168
         Object.keys(media.ssrcs).forEach(ssrcNum => {
170
         Object.keys(media.ssrcs).forEach(ssrcNum => {
169
             const mediaSsrc = media.ssrcs[ssrcNum];
171
             const mediaSsrc = media.ssrcs[ssrcNum];
172
+            const ssrcLines = mediaSsrc.lines;
173
+            const sourceName = SDPUtil.parseSourceNameLine(ssrcLines);
170
 
174
 
171
             modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
175
             modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
172
-            modify.attrs({ ssrc: mediaSsrc.ssrc });
176
+            modify.attrs({
177
+                name: FeatureFlags.isSourceNameSignalingEnabled() ? sourceName : undefined,
178
+                ssrc: mediaSsrc.ssrc
179
+            });
173
 
180
 
174
-            // iterate over ssrc lines
175
-            mediaSsrc.lines.forEach(line => {
176
-                const idx = line.indexOf(' ');
177
-                const kv = line.substr(idx + 1);
181
+            // Only MSID attribute is sent
182
+            const msid = SDPUtil.parseMSIDAttribute(ssrcLines);
178
 
183
 
184
+            if (msid) {
179
                 modify.c('parameter');
185
                 modify.c('parameter');
180
-                if (kv.indexOf(':') === -1) {
181
-                    modify.attrs({ name: kv });
182
-                } else {
183
-                    const nv = kv.split(':', 2);
184
-                    const name = nv[0];
185
-                    const value = SDPUtil.filterSpecialChars(nv[1]);
186
-
187
-                    modify.attrs({ name });
188
-                    modify.attrs({ value });
189
-                }
190
-                modify.up(); // end of parameter
191
-            });
186
+                modify.attrs({ name: 'msid' });
187
+                modify.attrs({ value: msid });
188
+                modify.up();
189
+            }
190
+
192
             modify.up(); // end of source
191
             modify.up(); // end of source
193
         });
192
         });
194
 
193
 

+ 71
- 0
modules/sdp/SDPDiffer.spec.js Parādīt failu

1
+import { $iq } from 'strophe.js';
2
+
3
+import FeatureFlags from '../flags/FeatureFlags';
4
+
5
+import SDP from './SDP';
6
+import SDPDiffer from './SDPDiffer';
7
+
8
+describe('SDPDiffer', () => {
9
+    beforeEach(() => {
10
+        FeatureFlags.init({ });
11
+    });
12
+    describe('toJingle', () => {
13
+        /* eslint-disable max-len*/
14
+        const testSdpOld = [
15
+            'v=0\r\n',
16
+            'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n',
17
+            's=-\r\n',
18
+            't=0 0\r\n',
19
+            'a=group:BUNDLE audio video\r\n',
20
+            'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n',
21
+            'a=mid:audio\r\n',
22
+            'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n',
23
+            'a=ssrc:2002 cname:juejgy8a01\r\n',
24
+            'a=ssrc:2002 name:a8f7g30-a0\r\n',
25
+            'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n',
26
+            'a=mid:video\r\n'
27
+        ].join('');
28
+        const testSdpNew = [
29
+            'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n',
30
+            'a=mid:audio\r\n',
31
+            'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n',
32
+            'a=mid:video\r\n',
33
+            'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n',
34
+            'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n',
35
+            'a=ssrc:4004 cname:juejgy8a01\r\n',
36
+            'a=ssrc:4005 cname:juejgy8a01\r\n',
37
+            'a=ssrc:4004 name:a8f7g30-v0\r\n',
38
+            'a=ssrc:4005 name:a8f7g30-v0\r\n',
39
+            'a=ssrc-group:FID 4004 4005\r\n'
40
+        ].join('');
41
+        /* eslint-enable max-len*/
42
+
43
+        it('should include source names in added/removed sources', () => {
44
+            FeatureFlags.init({ sourceNameSignaling: true });
45
+
46
+            const newToOldDiff = new SDPDiffer(new SDP(testSdpNew), new SDP(testSdpOld));
47
+            const sourceRemoveIq = $iq({})
48
+                .c('jingle', { action: 'source-remove' });
49
+
50
+            newToOldDiff.toJingle(sourceRemoveIq);
51
+
52
+            const removedAudioSources = sourceRemoveIq.nodeTree
53
+                .querySelectorAll('description[media=\'audio\']>source');
54
+
55
+            expect(removedAudioSources[0].getAttribute('name')).toBe('a8f7g30-a0');
56
+
57
+            const oldToNewDiff = new SDPDiffer(new SDP(testSdpOld), new SDP(testSdpNew));
58
+            const sourceAddIq = $iq({})
59
+                .c('jingle', { action: 'source-add' });
60
+
61
+            oldToNewDiff.toJingle(sourceAddIq);
62
+
63
+            const addedVideoSources = sourceAddIq.nodeTree
64
+                .querySelectorAll('description[media=\'video\']>source');
65
+
66
+            expect(addedVideoSources.length).toBe(2);
67
+            expect(addedVideoSources[0].getAttribute('name')).toBe('a8f7g30-v0');
68
+            expect(addedVideoSources[1].getAttribute('name')).toBe('a8f7g30-v0');
69
+        });
70
+    });
71
+});

+ 31
- 0
modules/sdp/SDPUtil.js Parādīt failu

46
     parseMID(line) {
46
     parseMID(line) {
47
         return line.substring(6);
47
         return line.substring(6);
48
     },
48
     },
49
+
50
+    /**
51
+     * Finds the MSID attribute in the given array of SSRC attribute lines and returns the value.
52
+     *
53
+     * @param {string[]} ssrcLines - an array of lines similar to 'a:213123 msid:stream-id track-id'.
54
+     * @returns {undefined|string}
55
+     */
56
+    parseMSIDAttribute(ssrcLines) {
57
+        const msidLine = ssrcLines.find(line => line.indexOf(' msid:') > 0);
58
+
59
+        if (!msidLine) {
60
+            return undefined;
61
+        }
62
+
63
+        const v = msidLine.substring(msidLine.indexOf(' msid:') + 6 /* the length of ' msid:' */);
64
+
65
+        return SDPUtil.filterSpecialChars(v);
66
+    },
49
     parseMLine(line) {
67
     parseMLine(line) {
50
         const data = {};
68
         const data = {};
51
         const parts = line.substring(2).split(' ');
69
         const parts = line.substring(2).split(' ');
261
 
279
 
262
         return data;
280
         return data;
263
     },
281
     },
282
+
283
+    /**
284
+     * Gets the source name out of the name attribute "a=ssrc:254321 name:name1".
285
+     *
286
+     * @param {string[]} ssrcLines
287
+     * @returns {string | undefined}
288
+     */
289
+    parseSourceNameLine(ssrcLines) {
290
+        const sourceNameLine = ssrcLines.find(ssrcSdpLine => ssrcSdpLine.indexOf(' name:') > 0);
291
+
292
+        // Everything past the "name:" part
293
+        return sourceNameLine?.substring(sourceNameLine.indexOf(' name:') + 6);
294
+    },
264
     parseRTCPFB(line) {
295
     parseRTCPFB(line) {
265
         const parts = line.substr(10).split(' ');
296
         const parts = line.substr(10).split(' ');
266
         const data = {};
297
         const data = {};

+ 3
- 1
modules/xmpp/strophe.jingle.js Parādīt failu

10
     createJingleEvent
10
     createJingleEvent
11
 } from '../../service/statistics/AnalyticsEvents';
11
 } from '../../service/statistics/AnalyticsEvents';
12
 import XMPPEvents from '../../service/xmpp/XMPPEvents';
12
 import XMPPEvents from '../../service/xmpp/XMPPEvents';
13
+import FeatureFlags from '../flags/FeatureFlags';
13
 import Statistics from '../statistics/statistics';
14
 import Statistics from '../statistics/statistics';
14
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
15
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
15
 import RandomUtil from '../util/RandomUtil';
16
 import RandomUtil from '../util/RandomUtil';
32
 function _createSourceExtension(owner, sourceCompactJson) {
33
 function _createSourceExtension(owner, sourceCompactJson) {
33
     const node = $build('source', {
34
     const node = $build('source', {
34
         xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
35
         xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
35
-        ssrc: sourceCompactJson.s
36
+        ssrc: sourceCompactJson.s,
37
+        name: FeatureFlags.isSourceNameSignalingEnabled() ? sourceCompactJson.n : undefined
36
     });
38
     });
37
 
39
 
38
     if (sourceCompactJson.m) {
40
     if (sourceCompactJson.m) {

Notiek ielāde…
Atcelt
Saglabāt