Parcourir la source

feat(multi-stream-support) Handle SDP munging for multiple local/remote streams per ep. (#1868)

* feat(multi-stream-support) Handle SDP munging for multiple local/remote streams/ep.
A new SDPSimulcast class is added for handling SDP munging for local and remote descriptions to handle multiple streams for both local and remote endpoints. This new class will be used only for unified plan implentation. The sdp-simucast npm package will be used for plan-b and deprecated later when all the clients switch to unified plan.

* fix(build) fix building with TS code

Use the Babel preset for the webpack bundle since Babel 7 already
understands TS and ts-loader uses tsc instead, which we configure
differently.

* squash: Add unit test and address review comments.

Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
dev1
Jaya Allamsetty il y a 3 ans
Parent
révision
bafd6c7b9d
Aucun compte lié à l'adresse e-mail de l'auteur

+ 14
- 7
modules/RTC/TraceablePeerConnection.js Voir le fichier

@@ -17,6 +17,7 @@ import RtxModifier from '../sdp/RtxModifier';
17 17
 import SDP from '../sdp/SDP';
18 18
 import SDPUtil from '../sdp/SDPUtil';
19 19
 import SdpConsistency from '../sdp/SdpConsistency';
20
+import SdpSimulcast from '../sdp/SdpSimulcast.ts';
20 21
 import { SdpTransformWrap } from '../sdp/SdpTransformUtil';
21 22
 import * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
22 23
 
@@ -260,14 +261,20 @@ export default function TraceablePeerConnection(
260 261
     this.maxstats = options.maxstats;
261 262
 
262 263
     this.interop = new Interop();
263
-    const Simulcast = require('@jitsi/sdp-simulcast');
264 264
 
265
-    this.simulcast = new Simulcast(
266
-        {
267
-            numOfLayers: SIM_LAYER_RIDS.length,
268
-            explodeRemoteSimulcast: false,
269
-            usesUnifiedPlan: this._usesUnifiedPlan
270
-        });
265
+    if (this._usesUnifiedPlan) {
266
+        this.simulcast = new SdpSimulcast({ numOfLayers: SIM_LAYER_RIDS.length });
267
+    } else {
268
+        const Simulcast = require('@jitsi/sdp-simulcast');
269
+
270
+        this.simulcast = new Simulcast(
271
+            {
272
+                numOfLayers: SIM_LAYER_RIDS.length,
273
+                explodeRemoteSimulcast: false,
274
+                usesUnifiedPlan: false
275
+            });
276
+    }
277
+
271 278
     this.sdpConsistency = new SdpConsistency(this.toString());
272 279
 
273 280
     /**

+ 157
- 0
modules/sdp/SDPSimulcast.spec.js Voir le fichier

@@ -0,0 +1,157 @@
1
+import * as transform from 'sdp-transform';
2
+
3
+import * as MediaType from '../../service/RTC/MediaType';
4
+
5
+import { default as SampleSdpStrings } from './SampleSdpStrings.js';
6
+import SdpSimulcast from './SdpSimulcast.ts';
7
+
8
+
9
+const getVideoGroups = (parsedSdp, groupSemantics) => {
10
+    const videoMLine = parsedSdp.media.find(m => m.type === MediaType.VIDEO);
11
+
12
+    videoMLine.ssrcGroups = videoMLine.ssrcGroups || [];
13
+
14
+    return videoMLine.ssrcGroups.filter(g => g.semantics === groupSemantics);
15
+};
16
+
17
+const numVideoSsrcs = parsedSdp => {
18
+    const videoMLine = parsedSdp.media.find(m => m.type === MediaType.VIDEO);
19
+    const ssrcs = new Set(videoMLine.ssrcs?.map(ssrcInfo => ssrcInfo.id));
20
+
21
+    return ssrcs.size;
22
+};
23
+
24
+const parseSimLayers = parsedSdp => {
25
+    const videoMLine = parsedSdp.media.find(m => m.type === MediaType.VIDEO);
26
+    const simGroup = videoMLine.ssrcGroups?.find(group => group.semantics === 'SIM');
27
+
28
+    if (simGroup) {
29
+        return simGroup.ssrcs.split(' ').map(ssrc => parseInt(ssrc, 10));
30
+    }
31
+
32
+    return null;
33
+};
34
+
35
+describe('sdp-simulcast', () => {
36
+    const numLayers = 3;
37
+    let simulcast;
38
+
39
+    beforeEach(() => {
40
+        simulcast = new SdpSimulcast({ numOfLayers: numLayers });
41
+    });
42
+
43
+    describe('mungeLocalDescription', () => {
44
+        it('should add simulcast layers to the local sdp', () => {
45
+            const sdp = SampleSdpStrings.plainVideoSdp;
46
+            const desc = {
47
+                type: 'answer',
48
+                sdp: transform.write(sdp)
49
+            };
50
+
51
+            const newDesc = simulcast.mungeLocalDescription(desc);
52
+            const newSdp = transform.parse(newDesc.sdp);
53
+
54
+            expect(numVideoSsrcs(newSdp)).toEqual(numLayers);
55
+            const simGroup = getVideoGroups(newSdp, 'SIM')[0];
56
+
57
+            expect(simGroup.ssrcs.split(' ').length).toEqual(numLayers);
58
+        });
59
+
60
+        it('should add the cached SSRCs on subsequent sLD calls to the local sdp', () => {
61
+            const sdp = SampleSdpStrings.plainVideoSdp;
62
+            const desc = {
63
+                type: 'answer',
64
+                sdp: transform.write(sdp)
65
+            };
66
+
67
+            const newDesc = simulcast.mungeLocalDescription(desc);
68
+            const newSdp = transform.parse(newDesc.sdp);
69
+            const cachedSsrcs = parseSimLayers(newSdp);
70
+
71
+            // Call sLD again with the original description.
72
+            const secondDesc = simulcast.mungeLocalDescription(desc);
73
+            const secondSdp = transform.parse(secondDesc.sdp);
74
+
75
+            expect(parseSimLayers(secondSdp)).toEqual(cachedSsrcs);
76
+        });
77
+
78
+        describe('corner cases', () => {
79
+            it('should do nothing if the mline has no ssrcs in the local sdp', () => {
80
+                const sdp = SampleSdpStrings.plainVideoSdp;
81
+                const videoMLine = sdp.media.find(m => m.type === MediaType.VIDEO);
82
+
83
+                videoMLine.ssrcs = [];
84
+                const desc = {
85
+                    type: 'answer',
86
+                    sdp: transform.write(sdp)
87
+                };
88
+
89
+                const newDesc = simulcast.mungeLocalDescription(desc);
90
+                const newSdp = transform.parse(newDesc.sdp);
91
+
92
+                expect(numVideoSsrcs(newSdp)).toEqual(0);
93
+            });
94
+
95
+            it('should do nothing if the mline already has a SIM group and 3 ssrcs in the local sdp', () => {
96
+                const sdp = SampleSdpStrings.simulcastSdp;
97
+                const desc = {
98
+                    type: 'answer',
99
+                    sdp: transform.write(sdp)
100
+                };
101
+                const ssrcs = parseSimLayers(sdp);
102
+
103
+                const newDesc = simulcast.mungeLocalDescription(desc);
104
+                const newSdp = transform.parse(newDesc.sdp);
105
+
106
+                expect(parseSimLayers(newSdp)).toEqual(ssrcs);
107
+            });
108
+
109
+            it('should do nothing if the m-line has only recv-only ssrcs', () => {
110
+                const sdp = SampleSdpStrings.recvOnlySdp;
111
+                const desc = {
112
+                    type: 'answer',
113
+                    sdp: transform.write(sdp)
114
+                };
115
+                const newDesc = simulcast.mungeLocalDescription(desc);
116
+                const newSdp = transform.parse(newDesc.sdp);
117
+
118
+                expect(numVideoSsrcs(newSdp)).toEqual(1);
119
+            });
120
+        });
121
+    });
122
+
123
+    describe('mungeRemoteDescription', () => {
124
+        it('should implode remote simulcast SSRCs into one FID group', () => {
125
+            const sdp = SampleSdpStrings.simulcastRtxSdp;
126
+            const desc = {
127
+                type: 'offer',
128
+                sdp: transform.write(sdp)
129
+            };
130
+            const newDesc = simulcast.mungeRemoteDescription(desc);
131
+            const newSdp = transform.parse(newDesc.sdp);
132
+            const fidGroups = getVideoGroups(newSdp, 'FID');
133
+            const simGroups = getVideoGroups(newSdp, 'SIM');
134
+
135
+            expect(fidGroups.length).toEqual(1);
136
+            expect(simGroups.length).toEqual(0);
137
+            expect(fidGroups[0].ssrcs).toContain('1757014965');
138
+            expect(fidGroups[0].ssrcs).toContain('984899560');
139
+        });
140
+
141
+        it('should implode remote simulcast SSRCs without RTX into one primary SSRC', () => {
142
+            const sdp = SampleSdpStrings.simulcastNoRtxSdp;
143
+            const desc = {
144
+                type: 'offer',
145
+                sdp: transform.write(sdp)
146
+            };
147
+            const newDesc = simulcast.mungeRemoteDescription(desc);
148
+            const newSdp = transform.parse(newDesc.sdp);
149
+            const fidGroups = getVideoGroups(newSdp, 'FID');
150
+            const simGroups = getVideoGroups(newSdp, 'SIM');
151
+
152
+            expect(fidGroups.length).toEqual(0);
153
+            expect(simGroups.length).toEqual(0);
154
+            expect(numVideoSsrcs(newSdp)).toEqual(1);
155
+        });
156
+    });
157
+});

+ 34
- 1
modules/sdp/SampleSdpStrings.js Voir le fichier

@@ -185,7 +185,7 @@ const simulcastRtxVideoMLineSdp = ''
185 185
 + 'a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n'
186 186
 + 'a=setup:passive\r\n'
187 187
 + 'a=mid:video\r\n'
188
-+ 'a=sendrecv\r\n'
188
++ 'a=sendonly\r\n'
189 189
 + 'a=ice-ufrag:adPg\r\n'
190 190
 + 'a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n'
191 191
 + 'a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n'
@@ -371,9 +371,38 @@ const videoLineP2pFF = ''
371 371
 + 'a=ssrc:984899560 cname:peDGrDD6WsxUOki/\r\n'
372 372
 + 'a=rtcp-mux\r\n';
373 373
 
374
+// An sdp video mline with 3 simulcast streams
375
+const simulcastVideoMLineNoRtxSdp = ''
376
++ 'm=video 9 RTP/SAVPF 100\r\n'
377
++ 'c=IN IP4 0.0.0.0\r\n'
378
++ 'a=rtpmap:100 VP8/90000\r\n'
379
++ 'a=rtcp:9 IN IP4 0.0.0.0\r\n'
380
++ 'a=rtcp-fb:100 ccm fir\r\n'
381
++ 'a=rtcp-fb:100 nack\r\n'
382
++ 'a=rtcp-fb:100 nack pli\r\n'
383
++ 'a=rtcp-fb:100 goog-remb\r\n'
384
++ 'a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n'
385
++ 'a=setup:passive\r\n'
386
++ 'a=mid:video\r\n'
387
++ 'a=sendonly\r\n'
388
++ 'a=ice-ufrag:adPg\r\n'
389
++ 'a=ice-pwd:Xsr05Mq8S7CR44DAnusZE26F\r\n'
390
++ 'a=fingerprint:sha-256 6A:39:DE:11:24:AD:2E:4E:63:D6:69:D3:85:05:53:C7:3C:38:A4:B7:91:74:C0:91:44:FC:94:63:7F:01:AB:A9\r\n'
391
++ 'a=ssrc:1757014965 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n'
392
++ 'a=ssrc:1757014965 cname:peDGrDD6WsxUOki/\r\n'
393
++ 'a=ssrc:1479742055 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n'
394
++ 'a=ssrc:1479742055 cname:peDGrDD6WsxUOki/\r\n'
395
++ 'a=ssrc:1089111804 msid:0836cc8e-a7bb-47e9-affb-0599414bc56d bdbd2c0a-7959-4578-8db5-9a6a1aec4ecf\r\n'
396
++ 'a=ssrc:1089111804 cname:peDGrDD6WsxUOki/\r\n'
397
++ 'a=ssrc-group:SIM 1757014965 1479742055 1089111804\r\n'
398
++ 'a=rtcp-mux\r\n';
399
+
374 400
 // A full sdp string representing a client doing simulcast
375 401
 const simulcastSdpStr = baseSessionSdp + baseAudioMLineSdp + simulcastVideoMLineSdp + baseDataMLineSdp;
376 402
 
403
+// A full sdp string representing a remote client doing simucast when RTX is not negotiated with the jvb.
404
+const simulcastNoRtxSdpStr = baseSessionSdp + baseAudioMLineSdp + simulcastVideoMLineNoRtxSdp;
405
+
377 406
 // A full sdp string representing a client doing simulcast and rtx
378 407
 const simulcastRtxSdpStr = baseSessionSdp + baseAudioMLineSdp + simulcastRtxVideoMLineSdp + baseDataMLineSdp;
379 408
 
@@ -403,6 +432,10 @@ export default {
403 432
         return transform.parse(simulcastSdpStr);
404 433
     },
405 434
 
435
+    get simulcastNoRtxSdp() {
436
+        return transform.parse(simulcastNoRtxSdpStr);
437
+    },
438
+
406 439
     get simulcastRtxSdp() {
407 440
         return transform.parse(simulcastRtxSdpStr);
408 441
     },

+ 317
- 0
modules/sdp/SdpSimulcast.ts Voir le fichier

@@ -0,0 +1,317 @@
1
+import MediaDirection from '../../service/RTC/MediaDirection';
2
+import * as MediaType from '../../service/RTC/MediaType';
3
+
4
+import * as transform from 'sdp-transform';
5
+
6
+const DEFAULT_NUM_OF_LAYERS = 3;
7
+
8
+interface Description {
9
+    type: RTCSdpType;
10
+    sdp: string;
11
+}
12
+
13
+interface Options {
14
+    numOfLayers?: number
15
+}
16
+
17
+/**
18
+ * This class handles SDP munging for enabling simulcast for local video streams in Unified plan. A set of random SSRCs
19
+ * are generated for the higher layer streams and they are cached for a given mid. The cached SSRCs are then reused on
20
+ * the subsequent iterations while munging the local description. This class also handles imploding of the simulcast
21
+ * SSRCs for remote endpoints into the primary FID group in remote description since Jicofo signals all SSRCs relevant
22
+ * to a given endpoint.
23
+ */
24
+export default class SdpSimulcast {
25
+    private _options: Options;
26
+    private _ssrcCache: Map<string, Array<number>>;
27
+
28
+    /**
29
+     * Creates a new instance.
30
+     *
31
+     * @param options
32
+     */
33
+    constructor(options: Options) {
34
+        this._options = options;
35
+        this._ssrcCache = new Map();
36
+
37
+        if (!this._options.numOfLayers) {
38
+            this._options.numOfLayers = DEFAULT_NUM_OF_LAYERS;
39
+        }
40
+    }
41
+
42
+    /**
43
+     * Updates the given media description using the SSRCs that were cached for the mid associated
44
+     * with the media description and returns the modified media description.
45
+     *
46
+     * @param mLine
47
+     * @returns
48
+     */
49
+     _fillSsrcsFromCache(mLine: transform.MediaDescription) : any {
50
+        const mid = mLine.mid;
51
+        const cachedSsrcs = this._ssrcCache.get(mid);
52
+        const newSsrcs = this._parseSimLayers(mLine);
53
+        const newMsid = this._getSsrcAttribute(mLine, newSsrcs[0], 'msid');
54
+        const newCname = this._getSsrcAttribute(mLine, newSsrcs[0], 'cname');
55
+
56
+        mLine.ssrcs = [];
57
+        mLine.ssrcGroups = [];
58
+
59
+        for (const ssrc of cachedSsrcs) {
60
+            mLine.ssrcs.push({
61
+                id: ssrc,
62
+                attribute: 'msid',
63
+                value: newMsid
64
+            });
65
+            mLine.ssrcs.push({
66
+                id: ssrc,
67
+                attribute: 'cname',
68
+                value: newCname
69
+            });
70
+        }
71
+
72
+        mLine.ssrcGroups.push({
73
+            semantics: 'SIM',
74
+            ssrcs: cachedSsrcs.join(' ')
75
+        });
76
+
77
+        return mLine;
78
+    }
79
+
80
+    /**
81
+     * Generates a new set of SSRCs for the higher simulcast layers/streams and adds the attributes and SIM group to
82
+     * the given media description and returns the modified media description.
83
+     *
84
+     * @param mLine
85
+     * @param primarySsrc
86
+     * @returns
87
+     */
88
+    _generateNewSsrcsForSimulcast(mLine: transform.MediaDescription, primarySsrc: number) : any {
89
+        const cname = this._getSsrcAttribute(mLine, primarySsrc, 'cname');
90
+        let msid = this._getSsrcAttribute(mLine, primarySsrc, 'msid');
91
+        const addAssociatedAttributes = (mLine: transform.MediaDescription, ssrc: number) => {
92
+            mLine.ssrcs.push({
93
+                id: ssrc,
94
+                attribute: 'cname',
95
+                value: cname
96
+            });
97
+            mLine.ssrcs.push({
98
+                id: ssrc,
99
+                attribute: 'msid',
100
+                value: msid
101
+            });
102
+        }
103
+
104
+        // In Unified-plan mode, the a=ssrc lines with the msid attribute are not present (only cname attributes are
105
+        // present) in the answers that Chrome and Safari generate for an offer received from Jicofo. Generate these
106
+        // a=ssrc lines using the msid values from the a=msid line.
107
+        if (!msid) {
108
+            msid = mLine.msid;
109
+            const primarySsrcs = mLine.ssrcs;
110
+
111
+            primarySsrcs.forEach(ssrc => {
112
+                mLine.ssrcs.push({
113
+                    id: ssrc.id,
114
+                    attribute: 'msid',
115
+                    value: msid
116
+                });
117
+            })
118
+        }
119
+
120
+        // Generate SIM layers.
121
+        const simSsrcs = [];
122
+    
123
+        for (let i = 0; i < this._options.numOfLayers - 1; ++i) {
124
+            const simSsrc = this._generateSsrc();
125
+
126
+            addAssociatedAttributes(mLine, simSsrc);
127
+            simSsrcs.push(simSsrc);
128
+        }
129
+
130
+        mLine.ssrcGroups = mLine.ssrcGroups || [];
131
+        mLine.ssrcGroups.push({
132
+            semantics: 'SIM',
133
+            ssrcs: primarySsrc + ' ' + simSsrcs.join(' ')
134
+        });
135
+    
136
+        return mLine;
137
+    }
138
+
139
+    /**
140
+     * Returns a random number to be used for the SSRC.
141
+     *
142
+     * @returns
143
+     */
144
+    _generateSsrc() : number {
145
+        const max = 0xffffffff;
146
+
147
+        return Math.floor(Math.random() * max);
148
+    }
149
+
150
+    /**
151
+     * Returns the requested attribute value for a SSRC from a given media description.
152
+     *
153
+     * @param mLine
154
+     * @param ssrc
155
+     * @param attributeName
156
+     * @returns
157
+     */
158
+    _getSsrcAttribute(mLine: transform.MediaDescription, ssrc: number, attributeName: string) : string | undefined {
159
+        return mLine.ssrcs?.find(
160
+            ssrcInfo => Number(ssrcInfo.id) === ssrc
161
+            && ssrcInfo.attribute === attributeName)?.value;
162
+    }
163
+
164
+    /**
165
+     * Returns an array of all the primary SSRCs in the SIM group for a given media description.
166
+     *
167
+     * @param mLine
168
+     * @returns
169
+     */
170
+    _parseSimLayers(mLine: transform.MediaDescription) : Array<number> | null {
171
+        const simGroup = mLine.ssrcGroups?.find(group => group.semantics === 'SIM');
172
+
173
+        if (simGroup) {
174
+            return simGroup.ssrcs.split(' ').map(ssrc => Number(ssrc));
175
+        }
176
+
177
+        if (mLine.ssrcs?.length) {
178
+            return [ Number(mLine.ssrcs[0].id) ];
179
+        }
180
+
181
+        return null;
182
+    }
183
+
184
+    /**
185
+     * Munges the given media description to enable simulcast for the video media sections that are in either have
186
+     * SENDRECV or SENDONLY as the media direction thereby ignoring all the RECVONLY transceivers created for remote
187
+     * endpoints.
188
+     * NOTE: This needs to be called only when simulcast is enabled.
189
+     *
190
+     * @param description
191
+     * @returns
192
+     */
193
+    mungeLocalDescription(description: Description) : Description {
194
+        if (!description || !description.sdp) {
195
+            return description;
196
+        }
197
+        const session = transform.parse(description.sdp);
198
+
199
+        for (let media of session.media) {
200
+            // Ignore recvonly and inactive transceivers created for remote sources.
201
+            if (media.direction === MediaDirection.RECVONLY || media.direction === MediaDirection.INACTIVE) {
202
+                continue;
203
+            }
204
+
205
+            // Ignore audio m-lines.
206
+            if (media.type !== MediaType.VIDEO) {
207
+                continue;
208
+            }
209
+            const mid = media.mid;
210
+            const numSsrcs = new Set(media.ssrcs?.map(ssrcInfo => ssrcInfo.id));
211
+            const numGroups = media.ssrcGroups?.length ?? 0;
212
+            let primarySsrc: number;
213
+
214
+            // Do not munge if the description has no ssrcs or if simulcast is already enabled.
215
+            if (numSsrcs.size === 0 || numSsrcs.size > 2 || (numSsrcs.size === 2 && numGroups === 0)) {
216
+                continue;
217
+            }
218
+            if (numSsrcs.size === 1) {
219
+                primarySsrc = Number(media.ssrcs[0]?.id);
220
+            } else {
221
+                const fidGroup = media.ssrcGroups.find(group => group.semantics === 'FID');
222
+
223
+                if (fidGroup) {
224
+                    primarySsrc = Number(fidGroup.ssrcs.split(' ')[0]);
225
+                }
226
+            }
227
+
228
+            if (this._ssrcCache.has(mid)) {
229
+                media = this._fillSsrcsFromCache(media);
230
+            } else {
231
+                media = this._generateNewSsrcsForSimulcast(media, primarySsrc);
232
+                const simulcastSsrcs = this._parseSimLayers(media);
233
+
234
+                // Update the SSRCs in the cache so that they can re-used for the same mid again.
235
+                this._ssrcCache.set(mid, simulcastSsrcs);
236
+            }
237
+        }
238
+
239
+        return new RTCSessionDescription({
240
+            type: description.type,
241
+            sdp: transform.write(session)
242
+        });
243
+    }
244
+
245
+    /**
246
+     * Munges the given media description by removing the SSRCs and related FID groups for the higher layer streams.
247
+     *
248
+     * @param description
249
+     * @returns
250
+     */
251
+    mungeRemoteDescription(description: Description) : Description {
252
+        if (!description || !description.sdp) {
253
+            return description;
254
+        }
255
+
256
+        const session = transform.parse(description.sdp);
257
+
258
+        for (const media of session.media) {
259
+            if (media.type !== MediaType.VIDEO) {
260
+                continue;
261
+            }
262
+
263
+            if (media.direction !== MediaDirection.SENDONLY) {
264
+                continue;
265
+            }
266
+
267
+            // Ignore m-lines that do not have any SSRCs or SSRC groups. These are the ones associated with remote
268
+            // sources that have left the call. These will be recycled when a new remote source joins the call.
269
+            if (!media.ssrcGroups?.length || !media?.ssrcs.length) {
270
+                continue;
271
+            }
272
+
273
+            // Cache the SSRCs and the source groups.
274
+            const mungedSsrcs = new Set(media.ssrcs.slice());
275
+            const mungedSsrcGroups = new Set(media.ssrcGroups.slice());
276
+            const fidGroups = media.ssrcGroups.filter(group => group.semantics === 'FID');
277
+            const simGroup = media.ssrcGroups.find(group => group.semantics === 'SIM');
278
+            const primarySsrc = simGroup?.ssrcs.split(' ')[0];;
279
+
280
+            // When simulcast and RTX are both enabled.
281
+            if (fidGroups.length && simGroup) {
282
+                const fidGroup = fidGroups.find(group => group.ssrcs.includes(primarySsrc));
283
+                const secondarySsrc = fidGroup.ssrcs.split(' ')[1];
284
+
285
+                for (const ssrcGroup of media.ssrcGroups) {
286
+                    if (ssrcGroup !== fidGroup) {
287
+                        mungedSsrcGroups.delete(ssrcGroup);
288
+                    }
289
+                }
290
+                for (const ssrc of media.ssrcs) {
291
+                    if (ssrc.id.toString() !== primarySsrc
292
+                        && ssrc.id.toString() !== secondarySsrc) {
293
+                        mungedSsrcs.delete(ssrc);
294
+                    }
295
+                }
296
+
297
+            // When simulcast is enabled but RTX is disabled.
298
+            } else if (simGroup) {
299
+                mungedSsrcGroups.delete(simGroup);
300
+
301
+                for (const ssrc of media.ssrcs) {
302
+                    if (ssrc.id.toString() !== primarySsrc) {
303
+                        mungedSsrcs.delete(ssrc);
304
+                    }
305
+                }
306
+            }
307
+
308
+            media.ssrcs = Array.from(mungedSsrcs);
309
+            media.ssrcGroups = Array.from(mungedSsrcGroups);
310
+        }
311
+
312
+        return new RTCSessionDescription ({
313
+            type: description.type,
314
+            sdp: transform.write(session)
315
+        });
316
+    }
317
+}

+ 325
- 366
package-lock.json
Fichier diff supprimé car celui-ci est trop grand
Voir le fichier


+ 8
- 2
package.json Voir le fichier

@@ -38,7 +38,9 @@
38 38
     "@babel/core": "7.16.0",
39 39
     "@babel/eslint-parser": "7.16.0",
40 40
     "@babel/preset-env": "7.16.0",
41
+    "@babel/preset-typescript": "7.16.7",
41 42
     "@jitsi/eslint-config": "4.0.0",
43
+    "@types/sdp-transform": "2.4.5",
42 44
     "babel-loader": "8.2.3",
43 45
     "core-js": "3.19.1",
44 46
     "eslint": "8.1.0",
@@ -51,7 +53,6 @@
51 53
     "karma-webpack": "5.0.0",
52 54
     "process": "0.11.10",
53 55
     "string-replace-loader": "3.0.3",
54
-    "ts-loader": "9.2.5",
55 56
     "typescript": "4.3.5",
56 57
     "webpack": "5.57.1",
57 58
     "webpack-bundle-analyzer": "4.4.2",
@@ -69,6 +70,11 @@
69 70
   },
70 71
   "browser": "dist/umd/lib-jitsi-meet.min.js",
71 72
   "module": "dist/esm/JitsiMeetJS.js",
72
-  "files": [ "dist", "types", "connection_optimization/external_connect.js", "modules/browser/capabilities.json" ],
73
+  "files": [
74
+    "dist",
75
+    "types",
76
+    "connection_optimization/external_connect.js",
77
+    "modules/browser/capabilities.json"
78
+  ],
73 79
   "license": "Apache-2.0"
74 80
 }

+ 1
- 0
tsconfig.json Voir le fichier

@@ -8,6 +8,7 @@
8 8
     "allowJs": true,
9 9
     "skipLibCheck": true,
10 10
     "esModuleInterop": true,
11
+    "moduleResolution": "node",
11 12
     "allowSyntheticDefaultImports": true,
12 13
     "lib": [
13 14
       "esnext",

+ 82
- 0
types/auto/modules/sdp/SdpSimulcast.d.ts Voir le fichier

@@ -0,0 +1,82 @@
1
+import * as transform from 'sdp-transform';
2
+interface Description {
3
+    type: RTCSdpType;
4
+    sdp: string;
5
+}
6
+interface Options {
7
+    numOfLayers?: number;
8
+}
9
+/**
10
+ * This class handles SDP munging for enabling simulcast for local video streams in Unified plan. A set of random SSRCs
11
+ * are generated for the higher layer streams and they are cached for a given mid. The cached SSRCs are then reused on
12
+ * the subsequent iterations while munging the local description. This class also handles imploding of the simulcast
13
+ * SSRCs for remote endpoints into the primary FID group in remote description since Jicofo signals all SSRCs relevant
14
+ * to a given endpoint.
15
+ */
16
+export default class SdpSimulcast {
17
+    private _options;
18
+    private _ssrcCache;
19
+    /**
20
+     * Creates a new instance.
21
+     *
22
+     * @param options
23
+     */
24
+    constructor(options: Options);
25
+    /**
26
+     * Updates the given media description using the SSRCs that were cached for the mid associated
27
+     * with the media description and returns the modified media description.
28
+     *
29
+     * @param mLine
30
+     * @returns
31
+     */
32
+    _fillSsrcsFromCache(mLine: transform.MediaDescription): any;
33
+    /**
34
+     * Generates a new set of SSRCs for the higher simulcast layers/streams and adds the attributes and SIM group to
35
+     * the given media description and returns the modified media description.
36
+     *
37
+     * @param mLine
38
+     * @param primarySsrc
39
+     * @returns
40
+     */
41
+    _generateNewSsrcsForSimulcast(mLine: transform.MediaDescription, primarySsrc: number): any;
42
+    /**
43
+     * Returns a random number to be used for the SSRC.
44
+     *
45
+     * @returns
46
+     */
47
+    _generateSsrc(): number;
48
+    /**
49
+     * Returns the requested attribute value for a SSRC from a given media description.
50
+     *
51
+     * @param mLine
52
+     * @param ssrc
53
+     * @param attributeName
54
+     * @returns
55
+     */
56
+    _getSsrcAttribute(mLine: transform.MediaDescription, ssrc: number, attributeName: string): string | undefined;
57
+    /**
58
+     * Returns an array of all the primary SSRCs in the SIM group for a given media description.
59
+     *
60
+     * @param mLine
61
+     * @returns
62
+     */
63
+    _parseSimLayers(mLine: transform.MediaDescription): Array<number> | null;
64
+    /**
65
+     * Munges the given media description to enable simulcast for the video media sections that are in either have
66
+     * SENDRECV or SENDONLY as the media direction thereby ignoring all the RECVONLY transceivers created for remote
67
+     * endpoints.
68
+     * NOTE: This needs to be called only when simulcast is enabled.
69
+     *
70
+     * @param description
71
+     * @returns
72
+     */
73
+    mungeLocalDescription(description: Description): Description;
74
+    /**
75
+     * Munges the given media description by removing the SSRCs and related FID groups for the higher layer streams.
76
+     *
77
+     * @param description
78
+     * @returns
79
+     */
80
+    mungeRemoteDescription(description: Description): Description;
81
+}
82
+export {};

+ 2
- 1
types/auto/modules/sdp/SdpTransformUtil.d.ts Voir le fichier

@@ -33,7 +33,7 @@ export class SdpTransformWrap {
33 33
      * @param {string} rawSDP the SDP in raw text format.
34 34
      */
35 35
     constructor(rawSDP: string);
36
-    parsedSDP: any;
36
+    parsedSDP: transform.SessionDescription;
37 37
     /**
38 38
      * Selects all the m-lines from the SDP for a given media type.
39 39
      *
@@ -50,6 +50,7 @@ export class SdpTransformWrap {
50 50
      */
51 51
     toRawSDP(): string;
52 52
 }
53
+import * as transform from "sdp-transform";
53 54
 /**
54 55
  * A wrapper around 'sdp-transform' media description object which provides
55 56
  * utility methods for common SDP/SSRC related operations.

+ 5
- 5
webpack-shared-config.js Voir le fichier

@@ -9,6 +9,9 @@ module.exports = (minimize, analyzeBundle) => {
9 9
     return {
10 10
         // The inline-source-map is used to allow debugging the unit tests with Karma
11 11
         devtool: minimize ? 'source-map' : 'inline-source-map',
12
+        resolve: {
13
+            extensions: [ '', '.js', '.ts' ]
14
+        },
12 15
         mode: minimize ? 'production' : 'development',
13 16
         module: {
14 17
             rules: [ {
@@ -49,14 +52,11 @@ module.exports = (minimize, analyzeBundle) => {
49 52
                                     safari: 14
50 53
                                 }
51 54
                             }
52
-                        ]
55
+                        ],
56
+                        '@babel/preset-typescript'
53 57
                     ]
54 58
                 },
55 59
                 test: /\.(js|ts)$/
56
-            }, {
57
-                exclude: /node_modules/,
58
-                test: /\.ts$/,
59
-                use: 'ts-loader'
60 60
             } ]
61 61
         },
62 62
         node: {

Chargement…
Annuler
Enregistrer