|
@@ -1,458 +0,0 @@
|
1
|
|
-/* global RTCRtpReceiver */
|
2
|
|
-
|
3
|
|
-import sdpTransform from 'sdp-transform';
|
4
|
|
-
|
5
|
|
-/**
|
6
|
|
- * Extract RTP capabilities from remote description.
|
7
|
|
- * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
8
|
|
- * @return {RTCRtpCapabilities}
|
9
|
|
- */
|
10
|
|
-export function extractCapabilities(sdpObject) {
|
11
|
|
- // Map of RtpCodecParameters indexed by payload type.
|
12
|
|
- const codecsMap = new Map();
|
13
|
|
-
|
14
|
|
- // Array of RtpHeaderExtensions.
|
15
|
|
- const headerExtensions = [];
|
16
|
|
-
|
17
|
|
- for (const m of sdpObject.media) {
|
18
|
|
- // Media kind.
|
19
|
|
- const kind = m.type;
|
20
|
|
-
|
21
|
|
- if (kind !== 'audio' && kind !== 'video') {
|
22
|
|
- continue; // eslint-disable-line no-continue
|
23
|
|
- }
|
24
|
|
-
|
25
|
|
- // Get codecs.
|
26
|
|
- for (const rtp of m.rtp) {
|
27
|
|
- const codec = {
|
28
|
|
- clockRate: rtp.rate,
|
29
|
|
- kind,
|
30
|
|
- mimeType: `${kind}/${rtp.codec}`,
|
31
|
|
- name: rtp.codec,
|
32
|
|
- numChannels: rtp.encoding || 1,
|
33
|
|
- parameters: {},
|
34
|
|
- preferredPayloadType: rtp.payload,
|
35
|
|
- rtcpFeedback: []
|
36
|
|
- };
|
37
|
|
-
|
38
|
|
- codecsMap.set(codec.preferredPayloadType, codec);
|
39
|
|
- }
|
40
|
|
-
|
41
|
|
- // Get codec parameters.
|
42
|
|
- for (const fmtp of m.fmtp || []) {
|
43
|
|
- const parameters = sdpTransform.parseFmtpConfig(fmtp.config);
|
44
|
|
- const codec = codecsMap.get(fmtp.payload);
|
45
|
|
-
|
46
|
|
- if (!codec) {
|
47
|
|
- continue; // eslint-disable-line no-continue
|
48
|
|
- }
|
49
|
|
-
|
50
|
|
- codec.parameters = parameters;
|
51
|
|
- }
|
52
|
|
-
|
53
|
|
- // Get RTCP feedback for each codec.
|
54
|
|
- for (const fb of m.rtcpFb || []) {
|
55
|
|
- const codec = codecsMap.get(fb.payload);
|
56
|
|
-
|
57
|
|
- if (!codec) {
|
58
|
|
- continue; // eslint-disable-line no-continue
|
59
|
|
- }
|
60
|
|
-
|
61
|
|
- codec.rtcpFeedback.push({
|
62
|
|
- parameter: fb.subtype || '',
|
63
|
|
- type: fb.type
|
64
|
|
- });
|
65
|
|
- }
|
66
|
|
-
|
67
|
|
- // Get RTP header extensions.
|
68
|
|
- for (const ext of m.ext || []) {
|
69
|
|
- const preferredId = ext.value;
|
70
|
|
- const uri = ext.uri;
|
71
|
|
- const headerExtension = {
|
72
|
|
- kind,
|
73
|
|
- uri,
|
74
|
|
- preferredId
|
75
|
|
- };
|
76
|
|
-
|
77
|
|
- // Check if already present.
|
78
|
|
- const duplicated = headerExtensions.find(savedHeaderExtension =>
|
79
|
|
- headerExtension.kind === savedHeaderExtension.kind
|
80
|
|
- && headerExtension.uri === savedHeaderExtension.uri
|
81
|
|
- );
|
82
|
|
-
|
83
|
|
- if (!duplicated) {
|
84
|
|
- headerExtensions.push(headerExtension);
|
85
|
|
- }
|
86
|
|
- }
|
87
|
|
- }
|
88
|
|
-
|
89
|
|
- return {
|
90
|
|
- codecs: Array.from(codecsMap.values()),
|
91
|
|
- fecMechanisms: [], // TODO
|
92
|
|
- headerExtensions
|
93
|
|
- };
|
94
|
|
-}
|
95
|
|
-
|
96
|
|
-/**
|
97
|
|
- * Extract DTLS parameters from remote description.
|
98
|
|
- * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
99
|
|
- * @return {RTCDtlsParameters}
|
100
|
|
- */
|
101
|
|
-export function extractDtlsParameters(sdpObject) {
|
102
|
|
- const media = getFirstActiveMediaSection(sdpObject);
|
103
|
|
- const fingerprint = media.fingerprint || sdpObject.fingerprint;
|
104
|
|
- let role;
|
105
|
|
-
|
106
|
|
- switch (media.setup) {
|
107
|
|
- case 'active':
|
108
|
|
- role = 'client';
|
109
|
|
- break;
|
110
|
|
- case 'passive':
|
111
|
|
- role = 'server';
|
112
|
|
- break;
|
113
|
|
- case 'actpass':
|
114
|
|
- role = 'auto';
|
115
|
|
- break;
|
116
|
|
- }
|
117
|
|
-
|
118
|
|
- return {
|
119
|
|
- role,
|
120
|
|
- fingerprints: [
|
121
|
|
- {
|
122
|
|
- algorithm: fingerprint.type,
|
123
|
|
- value: fingerprint.hash
|
124
|
|
- }
|
125
|
|
- ]
|
126
|
|
- };
|
127
|
|
-}
|
128
|
|
-
|
129
|
|
-/**
|
130
|
|
- * Extract ICE candidates from remote description.
|
131
|
|
- * NOTE: This implementation assumes a single BUNDLEd transport and rtcp-mux.
|
132
|
|
- * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
133
|
|
- * @return {sequence<RTCIceCandidate>}
|
134
|
|
- */
|
135
|
|
-export function extractIceCandidates(sdpObject) {
|
136
|
|
- const media = getFirstActiveMediaSection(sdpObject);
|
137
|
|
- const candidates = [];
|
138
|
|
-
|
139
|
|
- for (const c of media.candidates) {
|
140
|
|
- // Ignore RTCP candidates (we assume rtcp-mux).
|
141
|
|
- if (c.component !== 1) {
|
142
|
|
- continue; // eslint-disable-line no-continue
|
143
|
|
- }
|
144
|
|
-
|
145
|
|
- const candidate = {
|
146
|
|
- foundation: c.foundation,
|
147
|
|
- ip: c.ip,
|
148
|
|
- port: c.port,
|
149
|
|
- priority: c.priority,
|
150
|
|
- protocol: c.transport.toLowerCase(),
|
151
|
|
- type: c.type
|
152
|
|
- };
|
153
|
|
-
|
154
|
|
- candidates.push(candidate);
|
155
|
|
- }
|
156
|
|
-
|
157
|
|
- return candidates;
|
158
|
|
-}
|
159
|
|
-
|
160
|
|
-/**
|
161
|
|
- * Extract ICE parameters from remote description.
|
162
|
|
- * NOTE: This implementation assumes a single BUNDLEd transport.
|
163
|
|
- * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
164
|
|
- * @return {RTCIceParameters}
|
165
|
|
- */
|
166
|
|
-export function extractIceParameters(sdpObject) {
|
167
|
|
- const media = getFirstActiveMediaSection(sdpObject);
|
168
|
|
- const usernameFragment = media.iceUfrag;
|
169
|
|
- const password = media.icePwd;
|
170
|
|
- const icelite = sdpObject.icelite === 'ice-lite';
|
171
|
|
-
|
172
|
|
- return {
|
173
|
|
- icelite,
|
174
|
|
- password,
|
175
|
|
- usernameFragment
|
176
|
|
- };
|
177
|
|
-}
|
178
|
|
-
|
179
|
|
-/**
|
180
|
|
- * Extract MID values from remote description.
|
181
|
|
- * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
182
|
|
- * @return {map<String, String>} Ordered Map with MID as key and kind as value.
|
183
|
|
- */
|
184
|
|
-export function extractMids(sdpObject) {
|
185
|
|
- const midToKind = new Map();
|
186
|
|
-
|
187
|
|
- // Ignore disabled media sections.
|
188
|
|
- for (const m of sdpObject.media) {
|
189
|
|
- midToKind.set(m.mid, m.type);
|
190
|
|
- }
|
191
|
|
-
|
192
|
|
- return midToKind;
|
193
|
|
-}
|
194
|
|
-
|
195
|
|
-/**
|
196
|
|
- * Extract tracks information.
|
197
|
|
- * @param {Object} sdpObject - Remote SDP object generated by sdp-transform.
|
198
|
|
- * @return {Map}
|
199
|
|
- */
|
200
|
|
-export function extractTrackInfos(sdpObject) {
|
201
|
|
- // Map with info about receiving media.
|
202
|
|
- // - index: Media SSRC
|
203
|
|
- // - value: Object
|
204
|
|
- // - kind: 'audio' / 'video'
|
205
|
|
- // - ssrc: Media SSRC
|
206
|
|
- // - rtxSsrc: RTX SSRC (may be unset)
|
207
|
|
- // - streamId: MediaStream.jitsiRemoteId
|
208
|
|
- // - trackId: MediaStreamTrack.jitsiRemoteId
|
209
|
|
- // - cname: CNAME
|
210
|
|
- // @type {map<Number, Object>}
|
211
|
|
- const infos = new Map();
|
212
|
|
-
|
213
|
|
- // Map with stream SSRC as index and associated RTX SSRC as value.
|
214
|
|
- // @type {map<Number, Number>}
|
215
|
|
- const rtxMap = new Map();
|
216
|
|
-
|
217
|
|
- // Set of RTX SSRC values.
|
218
|
|
- const rtxSet = new Set();
|
219
|
|
-
|
220
|
|
- for (const m of sdpObject.media) {
|
221
|
|
- const kind = m.type;
|
222
|
|
-
|
223
|
|
- if (kind !== 'audio' && kind !== 'video') {
|
224
|
|
- continue; // eslint-disable-line no-continue
|
225
|
|
- }
|
226
|
|
-
|
227
|
|
- // Get RTX information.
|
228
|
|
- for (const ssrcGroup of m.ssrcGroups || []) {
|
229
|
|
- // Just consider FID.
|
230
|
|
- if (ssrcGroup.semantics !== 'FID') {
|
231
|
|
- continue; // eslint-disable-line no-continue
|
232
|
|
- }
|
233
|
|
-
|
234
|
|
- const ssrcs
|
235
|
|
- = ssrcGroup.ssrcs.split(' ').map(ssrc => Number(ssrc));
|
236
|
|
- const ssrc = ssrcs[0];
|
237
|
|
- const rtxSsrc = ssrcs[1];
|
238
|
|
-
|
239
|
|
- rtxMap.set(ssrc, rtxSsrc);
|
240
|
|
- rtxSet.add(rtxSsrc);
|
241
|
|
- }
|
242
|
|
-
|
243
|
|
- for (const ssrcObject of m.ssrcs || []) {
|
244
|
|
- const ssrc = ssrcObject.id;
|
245
|
|
-
|
246
|
|
- // Ignore RTX.
|
247
|
|
- if (rtxSet.has(ssrc)) {
|
248
|
|
- continue; // eslint-disable-line no-continue
|
249
|
|
- }
|
250
|
|
-
|
251
|
|
- let info = infos.get(ssrc);
|
252
|
|
-
|
253
|
|
- if (!info) {
|
254
|
|
- info = {
|
255
|
|
- kind,
|
256
|
|
- rtxSsrc: rtxMap.get(ssrc),
|
257
|
|
- ssrc
|
258
|
|
- };
|
259
|
|
-
|
260
|
|
- infos.set(ssrc, info);
|
261
|
|
- }
|
262
|
|
-
|
263
|
|
- switch (ssrcObject.attribute) {
|
264
|
|
- case 'cname': {
|
265
|
|
- info.cname = ssrcObject.value;
|
266
|
|
- break;
|
267
|
|
- }
|
268
|
|
- case 'msid': {
|
269
|
|
- const values = ssrcObject.value.split(' ');
|
270
|
|
- const streamId = values[0];
|
271
|
|
- const trackId = values[1];
|
272
|
|
-
|
273
|
|
- info.streamId = streamId;
|
274
|
|
- info.trackId = trackId;
|
275
|
|
- break;
|
276
|
|
- }
|
277
|
|
- case 'mslabel': {
|
278
|
|
- const streamId = ssrcObject.value;
|
279
|
|
-
|
280
|
|
- info.streamId = streamId;
|
281
|
|
- break;
|
282
|
|
- }
|
283
|
|
- case 'label': {
|
284
|
|
- const trackId = ssrcObject.value;
|
285
|
|
-
|
286
|
|
- info.trackId = trackId;
|
287
|
|
- break;
|
288
|
|
- }
|
289
|
|
- }
|
290
|
|
- }
|
291
|
|
- }
|
292
|
|
-
|
293
|
|
- return infos;
|
294
|
|
-}
|
295
|
|
-
|
296
|
|
-/**
|
297
|
|
- * Get local ORTC RTP capabilities filtered and adapted to the given remote RTP
|
298
|
|
- * capabilities.
|
299
|
|
- * @param {RTCRtpCapabilities} filterWithCapabilities - RTP capabilities to
|
300
|
|
- * filter with.
|
301
|
|
- * @return {RTCRtpCapabilities}
|
302
|
|
- */
|
303
|
|
-export function getLocalCapabilities(filterWithCapabilities) {
|
304
|
|
- const localFullCapabilities = RTCRtpReceiver.getCapabilities();
|
305
|
|
- const localCapabilities = {
|
306
|
|
- codecs: [],
|
307
|
|
- fecMechanisms: [],
|
308
|
|
- headerExtensions: []
|
309
|
|
- };
|
310
|
|
-
|
311
|
|
- // Map of RTX and codec payloads.
|
312
|
|
- // - index: Codec payloadType
|
313
|
|
- // - value: Associated RTX payloadType
|
314
|
|
- // @type {map<Number, Number>}
|
315
|
|
- const remoteRtxMap = new Map();
|
316
|
|
-
|
317
|
|
- // Set codecs.
|
318
|
|
- for (const remoteCodec of filterWithCapabilities.codecs) {
|
319
|
|
- const remoteCodecName = remoteCodec.name.toLowerCase();
|
320
|
|
-
|
321
|
|
- if (remoteCodecName === 'rtx') {
|
322
|
|
- remoteRtxMap.set(
|
323
|
|
- remoteCodec.parameters.apt, remoteCodec.preferredPayloadType);
|
324
|
|
-
|
325
|
|
- continue; // eslint-disable-line no-continue
|
326
|
|
- }
|
327
|
|
-
|
328
|
|
- const localCodec = localFullCapabilities.codecs.find(codec =>
|
329
|
|
- codec.name.toLowerCase() === remoteCodecName
|
330
|
|
- && codec.kind === remoteCodec.kind
|
331
|
|
- && codec.clockRate === remoteCodec.clockRate
|
332
|
|
- );
|
333
|
|
-
|
334
|
|
- if (!localCodec) {
|
335
|
|
- continue; // eslint-disable-line no-continue
|
336
|
|
- }
|
337
|
|
-
|
338
|
|
- const codec = {
|
339
|
|
- clockRate: localCodec.clockRate,
|
340
|
|
- kind: localCodec.kind,
|
341
|
|
- mimeType: `${localCodec.kind}/${localCodec.name}`,
|
342
|
|
- name: localCodec.name,
|
343
|
|
- numChannels: localCodec.numChannels || 1,
|
344
|
|
- parameters: {},
|
345
|
|
- preferredPayloadType: remoteCodec.preferredPayloadType,
|
346
|
|
- rtcpFeedback: []
|
347
|
|
- };
|
348
|
|
-
|
349
|
|
- for (const remoteParamName of Object.keys(remoteCodec.parameters)) {
|
350
|
|
- const remoteParamValue
|
351
|
|
- = remoteCodec.parameters[remoteParamName];
|
352
|
|
-
|
353
|
|
- for (const localParamName of Object.keys(localCodec.parameters)) {
|
354
|
|
- const localParamValue
|
355
|
|
- = localCodec.parameters[localParamName];
|
356
|
|
-
|
357
|
|
- if (localParamName !== remoteParamName) {
|
358
|
|
- continue; // eslint-disable-line no-continue
|
359
|
|
- }
|
360
|
|
-
|
361
|
|
- // TODO: We should consider much more cases here, but Edge
|
362
|
|
- // does not support many codec parameters.
|
363
|
|
- if (localParamValue === remoteParamValue) {
|
364
|
|
- // Use this RTP parameter.
|
365
|
|
- codec.parameters[localParamName] = localParamValue;
|
366
|
|
- break;
|
367
|
|
- }
|
368
|
|
- }
|
369
|
|
- }
|
370
|
|
-
|
371
|
|
- for (const remoteFb of remoteCodec.rtcpFeedback) {
|
372
|
|
- const localFb = localCodec.rtcpFeedback.find(fb =>
|
373
|
|
- fb.type === remoteFb.type
|
374
|
|
- && fb.parameter === remoteFb.parameter
|
375
|
|
- );
|
376
|
|
-
|
377
|
|
- if (localFb) {
|
378
|
|
- // Use this RTCP feedback.
|
379
|
|
- codec.rtcpFeedback.push(localFb);
|
380
|
|
- }
|
381
|
|
- }
|
382
|
|
-
|
383
|
|
- // Use this codec.
|
384
|
|
- localCapabilities.codecs.push(codec);
|
385
|
|
- }
|
386
|
|
-
|
387
|
|
- // Add RTX for video codecs.
|
388
|
|
- for (const codec of localCapabilities.codecs) {
|
389
|
|
- const payloadType = codec.preferredPayloadType;
|
390
|
|
-
|
391
|
|
- if (!remoteRtxMap.has(payloadType)) {
|
392
|
|
- continue; // eslint-disable-line no-continue
|
393
|
|
- }
|
394
|
|
-
|
395
|
|
- const rtxCodec = {
|
396
|
|
- clockRate: codec.clockRate,
|
397
|
|
- kind: codec.kind,
|
398
|
|
- mimeType: `${codec.kind}/rtx`,
|
399
|
|
- name: 'rtx',
|
400
|
|
- parameters: {
|
401
|
|
- apt: payloadType
|
402
|
|
- },
|
403
|
|
- preferredPayloadType: remoteRtxMap.get(payloadType),
|
404
|
|
- rtcpFeedback: []
|
405
|
|
- };
|
406
|
|
-
|
407
|
|
- // Add RTX codec.
|
408
|
|
- localCapabilities.codecs.push(rtxCodec);
|
409
|
|
- }
|
410
|
|
-
|
411
|
|
- // Add RTP header extensions.
|
412
|
|
- for (const remoteExtension of filterWithCapabilities.headerExtensions) {
|
413
|
|
- const localExtension
|
414
|
|
- = localFullCapabilities.headerExtensions.find(extension =>
|
415
|
|
- extension.kind === remoteExtension.kind
|
416
|
|
- && extension.uri === remoteExtension.uri
|
417
|
|
- );
|
418
|
|
-
|
419
|
|
- if (localExtension) {
|
420
|
|
- const extension = {
|
421
|
|
- kind: localExtension.kind,
|
422
|
|
- preferredEncrypt: Boolean(remoteExtension.preferredEncrypt),
|
423
|
|
- preferredId: remoteExtension.preferredId,
|
424
|
|
- uri: localExtension.uri
|
425
|
|
- };
|
426
|
|
-
|
427
|
|
- // Use this RTP header extension.
|
428
|
|
- localCapabilities.headerExtensions.push(extension);
|
429
|
|
- }
|
430
|
|
- }
|
431
|
|
-
|
432
|
|
- // Add FEC mechanisms.
|
433
|
|
- // NOTE: We don't support FEC yet and, in fact, neither does Edge.
|
434
|
|
- for (const remoteFecMechanism of filterWithCapabilities.fecMechanisms) {
|
435
|
|
- const localFecMechanism
|
436
|
|
- = localFullCapabilities.fecMechanisms.find(fec =>
|
437
|
|
- fec === remoteFecMechanism
|
438
|
|
- );
|
439
|
|
-
|
440
|
|
- if (localFecMechanism) {
|
441
|
|
- // Use this FEC mechanism.
|
442
|
|
- localCapabilities.fecMechanisms.push(localFecMechanism);
|
443
|
|
- }
|
444
|
|
- }
|
445
|
|
-
|
446
|
|
- return localCapabilities;
|
447
|
|
-}
|
448
|
|
-
|
449
|
|
-/**
|
450
|
|
- * Get the first acive media section.
|
451
|
|
- * @param {Object} sdpObject - SDP object generated by sdp-transform.
|
452
|
|
- * @return {Object} SDP media section as parsed by sdp-transform.
|
453
|
|
- */
|
454
|
|
-function getFirstActiveMediaSection(sdpObject) {
|
455
|
|
- return sdpObject.media.find(m =>
|
456
|
|
- m.iceUfrag && m.port !== 0
|
457
|
|
- );
|
458
|
|
-}
|