@webex/plugin-meetings 3.0.0-stream-classes.4 → 3.0.0-test.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/no-meeting-info.js +51 -0
- package/dist/common/errors/no-meeting-info.js.map +1 -0
- package/dist/common/errors/reclaim-host-role-errors.js +158 -0
- package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
- package/dist/common/errors/webex-errors.js +23 -3
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/logs/request.js +5 -1
- package/dist/common/logs/request.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +69 -9
- package/dist/constants.js.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.js +15 -0
- package/dist/interceptors/index.js.map +1 -0
- package/dist/interceptors/locusRetry.js +93 -0
- package/dist/interceptors/locusRetry.js.map +1 -0
- package/dist/interpretation/index.js +16 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +40 -11
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js +15 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/parser.js +42 -21
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/media/index.js +10 -6
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +13 -3
- package/dist/media/properties.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js +135 -330
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +4 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +2187 -1074
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +37 -25
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +34 -19
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +71 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/index.js +48 -23
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +25 -4
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +1 -1
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +17 -0
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +142 -57
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +2 -6
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +11 -0
- package/dist/member/util.js.map +1 -1
- package/dist/members/index.js +17 -1
- package/dist/members/index.js.map +1 -1
- package/dist/members/types.js.map +1 -1
- package/dist/members/util.js +15 -4
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +15 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +16 -2
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +222 -73
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +22 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/clusterReachability.js +356 -0
- package/dist/reachability/clusterReachability.js.map +1 -0
- package/dist/reachability/index.js +262 -432
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +1 -1
- package/dist/reachability/request.js.map +1 -1
- package/dist/reachability/util.js +29 -0
- package/dist/reachability/util.js.map +1 -0
- package/dist/reconnection-manager/index.js +113 -96
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +57 -25
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +5 -13
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +173 -81
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/rtcMetrics/index.js +68 -6
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +338 -289
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +296 -156
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
- package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
- package/dist/types/common/errors/webex-errors.d.ts +13 -1
- package/dist/types/common/logs/request.d.ts +2 -0
- package/dist/types/config.d.ts +1 -1
- package/dist/types/constants.d.ts +66 -13
- package/dist/types/index.d.ts +1 -1
- package/dist/types/interceptors/index.d.ts +2 -0
- package/dist/types/interceptors/locusRetry.d.ts +27 -0
- package/dist/types/locus-info/index.d.ts +1 -1
- package/dist/types/locus-info/parser.d.ts +3 -2
- package/dist/types/mediaQualityMetrics/config.d.ts +99 -223
- package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
- package/dist/types/meeting/index.d.ts +285 -34
- package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
- package/dist/types/meeting/muteState.d.ts +2 -8
- package/dist/types/meeting/request.d.ts +4 -1
- package/dist/types/meeting/util.d.ts +25 -1
- package/dist/types/meeting-info/index.d.ts +7 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
- package/dist/types/meetings/collection.d.ts +9 -0
- package/dist/types/meetings/index.d.ts +42 -14
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/members/types.d.ts +1 -0
- package/dist/types/members/util.d.ts +5 -0
- package/dist/types/metrics/constants.d.ts +15 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +2 -0
- package/dist/types/multistream/remoteMediaGroup.d.ts +2 -0
- package/dist/types/multistream/remoteMediaManager.d.ts +25 -1
- package/dist/types/multistream/sendSlotManager.d.ts +9 -0
- package/dist/types/reachability/clusterReachability.d.ts +109 -0
- package/dist/types/reachability/index.d.ts +59 -112
- package/dist/types/reachability/request.d.ts +1 -1
- package/dist/types/reachability/util.d.ts +8 -0
- package/dist/types/reconnection-manager/index.d.ts +10 -0
- package/dist/types/roap/index.d.ts +2 -1
- package/dist/types/roap/request.d.ts +2 -1
- package/dist/types/roap/turnDiscovery.d.ts +21 -4
- package/dist/types/rtcMetrics/index.d.ts +15 -1
- package/dist/types/statsAnalyzer/index.d.ts +28 -11
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +28 -4
- package/dist/types/webinar/collection.d.ts +16 -0
- package/dist/types/webinar/index.d.ts +5 -0
- package/dist/webinar/collection.js +44 -0
- package/dist/webinar/collection.js.map +1 -0
- package/dist/webinar/index.js +69 -0
- package/dist/webinar/index.js.map +1 -0
- package/package.json +3 -2
- package/src/common/errors/no-meeting-info.ts +24 -0
- package/src/common/errors/reclaim-host-role-errors.ts +134 -0
- package/src/common/errors/webex-errors.ts +19 -2
- package/src/common/logs/request.ts +5 -1
- package/src/config.ts +1 -1
- package/src/constants.ts +71 -6
- package/src/index.ts +5 -0
- package/src/interceptors/index.ts +3 -0
- package/src/interceptors/locusRetry.ts +67 -0
- package/src/interpretation/index.ts +18 -1
- package/src/locus-info/index.ts +52 -16
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/locus-info/parser.ts +47 -21
- package/src/media/index.ts +8 -6
- package/src/media/properties.ts +17 -2
- package/src/mediaQualityMetrics/config.ts +103 -238
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +1510 -529
- package/src/meeting/muteState.ts +34 -20
- package/src/meeting/request.ts +19 -1
- package/src/meeting/util.ts +97 -0
- package/src/meeting-info/index.ts +47 -20
- package/src/meeting-info/meeting-info-v2.ts +27 -5
- package/src/meeting-info/utilv2.ts +1 -1
- package/src/meetings/collection.ts +13 -0
- package/src/meetings/index.ts +112 -31
- package/src/meetings/util.ts +2 -8
- package/src/member/index.ts +9 -0
- package/src/member/util.ts +14 -0
- package/src/members/index.ts +29 -2
- package/src/members/types.ts +1 -0
- package/src/members/util.ts +15 -1
- package/src/metrics/constants.ts +14 -0
- package/src/multistream/mediaRequestManager.ts +4 -1
- package/src/multistream/remoteMediaGroup.ts +19 -0
- package/src/multistream/remoteMediaManager.ts +141 -18
- package/src/multistream/sendSlotManager.ts +29 -0
- package/src/reachability/clusterReachability.ts +320 -0
- package/src/reachability/index.ts +221 -382
- package/src/reachability/request.ts +1 -1
- package/src/reachability/util.ts +24 -0
- package/src/reconnection-manager/index.ts +87 -83
- package/src/roap/index.ts +60 -24
- package/src/roap/request.ts +3 -16
- package/src/roap/turnDiscovery.ts +112 -39
- package/src/rtcMetrics/index.ts +71 -5
- package/src/statsAnalyzer/index.ts +430 -427
- package/src/statsAnalyzer/mqaUtil.ts +317 -168
- package/src/webinar/collection.ts +31 -0
- package/src/webinar/index.ts +62 -0
- package/test/integration/spec/converged-space-meetings.js +7 -7
- package/test/integration/spec/journey.js +86 -104
- package/test/integration/spec/space-meeting.js +9 -9
- package/test/unit/spec/interceptors/locusRetry.ts +131 -0
- package/test/unit/spec/interpretation/index.ts +36 -3
- package/test/unit/spec/locus-info/index.js +205 -12
- package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +10 -0
- package/test/unit/spec/locus-info/parser.js +54 -13
- package/test/unit/spec/media/index.ts +20 -4
- package/test/unit/spec/media/properties.ts +2 -2
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +4027 -1075
- package/test/unit/spec/meeting/muteState.js +219 -67
- package/test/unit/spec/meeting/request.js +63 -12
- package/test/unit/spec/meeting/utils.js +93 -0
- package/test/unit/spec/meeting-info/index.js +180 -61
- package/test/unit/spec/meeting-info/meetinginfov2.js +196 -53
- package/test/unit/spec/meetings/collection.js +12 -0
- package/test/unit/spec/meetings/index.js +619 -206
- package/test/unit/spec/meetings/utils.js +35 -12
- package/test/unit/spec/member/index.js +8 -7
- package/test/unit/spec/member/util.js +32 -0
- package/test/unit/spec/members/index.js +130 -17
- package/test/unit/spec/members/utils.js +26 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
- package/test/unit/spec/multistream/remoteMediaGroup.ts +80 -1
- package/test/unit/spec/multistream/remoteMediaManager.ts +210 -3
- package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
- package/test/unit/spec/reachability/clusterReachability.ts +279 -0
- package/test/unit/spec/reachability/index.ts +505 -135
- package/test/unit/spec/reachability/util.ts +40 -0
- package/test/unit/spec/reconnection-manager/index.js +74 -17
- package/test/unit/spec/roap/index.ts +181 -61
- package/test/unit/spec/roap/request.ts +27 -3
- package/test/unit/spec/roap/turnDiscovery.ts +362 -101
- package/test/unit/spec/rtcMetrics/index.ts +57 -3
- package/test/unit/spec/stats-analyzer/index.js +1225 -12
- package/test/unit/spec/webinar/collection.ts +13 -0
- package/test/unit/spec/webinar/index.ts +60 -0
- package/test/utils/integrationTestUtils.js +4 -4
- package/test/utils/webex-test-users.js +12 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {cloneDeep, forEach, remove} from 'lodash';
|
|
3
3
|
import {EventMap} from 'typed-emitter';
|
|
4
4
|
import {MediaType} from '@webex/internal-media-core';
|
|
5
|
+
import {NamedMediaGroup} from '@webex/json-multistream';
|
|
5
6
|
|
|
6
7
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
7
8
|
import EventsScope from '../common/events/events-scope';
|
|
@@ -11,6 +12,7 @@ import {ReceiveSlot, CSI} from './receiveSlot';
|
|
|
11
12
|
import {ReceiveSlotManager} from './receiveSlotManager';
|
|
12
13
|
import {RemoteMediaGroup} from './remoteMediaGroup';
|
|
13
14
|
import {MediaRequestManager} from './mediaRequestManager';
|
|
15
|
+
import {NAMED_MEDIA_GROUP_TYPE_AUDIO} from '../constants';
|
|
14
16
|
|
|
15
17
|
export type PaneSize = RemoteVideoResolution;
|
|
16
18
|
export type LayoutId = string;
|
|
@@ -49,6 +51,7 @@ export interface Configuration {
|
|
|
49
51
|
|
|
50
52
|
layouts: {[key: LayoutId]: VideoLayout}; // a map of all available layouts, a layout can be set via setLayout() method
|
|
51
53
|
};
|
|
54
|
+
namedMediaGroup?: NamedMediaGroup;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/* Predefined layouts: */
|
|
@@ -173,6 +176,7 @@ export const DefaultConfiguration: Configuration = {
|
|
|
173
176
|
export enum Event {
|
|
174
177
|
// events for audio streams
|
|
175
178
|
AudioCreated = 'AudioCreated',
|
|
179
|
+
InterpretationAudioCreated = 'InterpretationAudioCreated',
|
|
176
180
|
ScreenShareAudioCreated = 'ScreenShareAudioCreated',
|
|
177
181
|
|
|
178
182
|
// events for video streams
|
|
@@ -221,7 +225,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
221
225
|
private currentLayout?: VideoLayout;
|
|
222
226
|
|
|
223
227
|
private slots: {
|
|
224
|
-
audio:
|
|
228
|
+
audio: {
|
|
229
|
+
main: ReceiveSlot[];
|
|
230
|
+
si: ReceiveSlot;
|
|
231
|
+
};
|
|
225
232
|
screenShare: {
|
|
226
233
|
audio: ReceiveSlot[];
|
|
227
234
|
video?: ReceiveSlot;
|
|
@@ -234,7 +241,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
234
241
|
};
|
|
235
242
|
|
|
236
243
|
private media: {
|
|
237
|
-
audio
|
|
244
|
+
audio: {
|
|
245
|
+
main?: RemoteMediaGroup;
|
|
246
|
+
si?: RemoteMediaGroup;
|
|
247
|
+
};
|
|
238
248
|
video: {
|
|
239
249
|
activeSpeakerGroups: {
|
|
240
250
|
[key: PaneGroupId]: RemoteMediaGroup;
|
|
@@ -277,7 +287,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
277
287
|
this.receiveSlotManager = receiveSlotManager;
|
|
278
288
|
this.mediaRequestManagers = mediaRequestManagers;
|
|
279
289
|
this.media = {
|
|
280
|
-
audio:
|
|
290
|
+
audio: {
|
|
291
|
+
main: undefined,
|
|
292
|
+
si: undefined,
|
|
293
|
+
},
|
|
281
294
|
video: {
|
|
282
295
|
activeSpeakerGroups: {},
|
|
283
296
|
memberPanes: {},
|
|
@@ -291,7 +304,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
291
304
|
this.checkConfigValidity();
|
|
292
305
|
|
|
293
306
|
this.slots = {
|
|
294
|
-
audio:
|
|
307
|
+
audio: {
|
|
308
|
+
main: [],
|
|
309
|
+
si: undefined,
|
|
310
|
+
},
|
|
295
311
|
screenShare: {
|
|
296
312
|
audio: [],
|
|
297
313
|
video: undefined,
|
|
@@ -389,8 +405,11 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
389
405
|
});
|
|
390
406
|
|
|
391
407
|
// release all audio receive slots
|
|
392
|
-
this.slots.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
393
|
-
this.slots.audio.length = 0;
|
|
408
|
+
this.slots.audio.main.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
409
|
+
this.slots.audio.main.length = 0;
|
|
410
|
+
if (this.slots.audio.si) {
|
|
411
|
+
this.receiveSlotManager.releaseSlot(this.slots.audio.si);
|
|
412
|
+
}
|
|
394
413
|
|
|
395
414
|
// release screen share slots
|
|
396
415
|
this.slots.screenShare.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
@@ -473,6 +492,8 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
473
492
|
if (!this.started) {
|
|
474
493
|
throw new Error('setLayout() called before start()');
|
|
475
494
|
}
|
|
495
|
+
LoggerProxy.logger.log(`RemoteMediaManager#setLayout --> new layout selected: ${layoutId}`);
|
|
496
|
+
|
|
476
497
|
this.currentLayoutId = layoutId;
|
|
477
498
|
this.currentLayout = cloneDeep(this.config.video.layouts[this.currentLayoutId]);
|
|
478
499
|
|
|
@@ -523,22 +544,54 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
523
544
|
this.mediaRequestManagers.video.commit();
|
|
524
545
|
}
|
|
525
546
|
|
|
547
|
+
/**
|
|
548
|
+
* Sets which named media group need receiving
|
|
549
|
+
* @param {MediaType} mediaType of the stream
|
|
550
|
+
* @param {number} languageCode of the stream. If the languageId is 0, the named media group request will be canceled,
|
|
551
|
+
* and only receive the main audio stream.
|
|
552
|
+
* @returns {void}
|
|
553
|
+
*/
|
|
554
|
+
public async setReceiveNamedMediaGroup(mediaType: MediaType, languageId: number) {
|
|
555
|
+
if (mediaType !== MediaType.AudioMain) {
|
|
556
|
+
throw new Error(`cannot set receive named media group which media type is ${mediaType}`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const value = languageId;
|
|
560
|
+
if (value === this.config.namedMediaGroup?.value) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this.config.namedMediaGroup = {
|
|
565
|
+
type: NAMED_MEDIA_GROUP_TYPE_AUDIO,
|
|
566
|
+
value,
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
if (!this.media.audio.si) {
|
|
570
|
+
await this.createInterpretationAudioMedia(true);
|
|
571
|
+
} else {
|
|
572
|
+
this.media.audio.si.setNamedMediaGroup(this.config.namedMediaGroup, true);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
526
576
|
/**
|
|
527
577
|
* Creates the audio slots
|
|
528
578
|
*/
|
|
529
579
|
private async createAudioMedia() {
|
|
530
|
-
// create
|
|
580
|
+
// create si audio request
|
|
581
|
+
await this.createInterpretationAudioMedia(false);
|
|
582
|
+
|
|
583
|
+
// create main audio receive slots
|
|
531
584
|
for (let i = 0; i < this.config.audio.numOfActiveSpeakerStreams; i += 1) {
|
|
532
585
|
// eslint-disable-next-line no-await-in-loop
|
|
533
586
|
const slot = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
|
|
534
587
|
|
|
535
|
-
this.slots.audio.push(slot);
|
|
588
|
+
this.slots.audio.main.push(slot);
|
|
536
589
|
}
|
|
537
590
|
|
|
538
|
-
// create a remote media group
|
|
539
|
-
this.media.audio = new RemoteMediaGroup(
|
|
591
|
+
// create a remote media group for main audio
|
|
592
|
+
this.media.audio.main = new RemoteMediaGroup(
|
|
540
593
|
this.mediaRequestManagers.audio,
|
|
541
|
-
this.slots.audio,
|
|
594
|
+
this.slots.audio.main,
|
|
542
595
|
255,
|
|
543
596
|
true
|
|
544
597
|
);
|
|
@@ -546,10 +599,40 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
546
599
|
this.emit(
|
|
547
600
|
{file: 'multistream/remoteMediaManager', function: 'createAudioMedia'},
|
|
548
601
|
Event.AudioCreated,
|
|
549
|
-
this.media.audio
|
|
602
|
+
this.media.audio.main
|
|
550
603
|
);
|
|
551
604
|
}
|
|
552
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Creates the audio slots for named media
|
|
608
|
+
*/
|
|
609
|
+
private async createInterpretationAudioMedia(commitRequest: boolean) {
|
|
610
|
+
// create slot for interpretation language audio
|
|
611
|
+
if (
|
|
612
|
+
this.config.namedMediaGroup?.type === NAMED_MEDIA_GROUP_TYPE_AUDIO &&
|
|
613
|
+
this.config.namedMediaGroup?.value
|
|
614
|
+
) {
|
|
615
|
+
this.slots.audio.si = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
|
|
616
|
+
|
|
617
|
+
// create a remote media group for si audio
|
|
618
|
+
this.media.audio.si = new RemoteMediaGroup(
|
|
619
|
+
this.mediaRequestManagers.audio,
|
|
620
|
+
[this.slots.audio.si],
|
|
621
|
+
255,
|
|
622
|
+
commitRequest,
|
|
623
|
+
{
|
|
624
|
+
namedMediaGroup: this.config.namedMediaGroup,
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
this.emit(
|
|
629
|
+
{file: 'multistream/remoteMediaManager', function: 'createInterpretationAudioMedia'},
|
|
630
|
+
Event.InterpretationAudioCreated,
|
|
631
|
+
this.media.audio.si
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
553
636
|
/**
|
|
554
637
|
* Creates receive slots required for receiving screen share audio and video
|
|
555
638
|
*/
|
|
@@ -725,10 +808,12 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
725
808
|
/**
|
|
726
809
|
* Logs the state of the receive slots
|
|
727
810
|
*/
|
|
728
|
-
private
|
|
811
|
+
private logMainVideoReceiveSlots() {
|
|
729
812
|
let logMessage = '';
|
|
730
813
|
forEach(this.receiveSlotAllocations.activeSpeaker, (group, groupName) => {
|
|
731
|
-
logMessage +=
|
|
814
|
+
logMessage += `\ngroup: ${groupName}\n${group.slots
|
|
815
|
+
.map((slot) => slot.logString)
|
|
816
|
+
.join(', ')}`;
|
|
732
817
|
});
|
|
733
818
|
|
|
734
819
|
logMessage += '\nreceiverSelected:\n';
|
|
@@ -737,10 +822,43 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
737
822
|
});
|
|
738
823
|
|
|
739
824
|
LoggerProxy.logger.log(
|
|
740
|
-
`RemoteMediaManager#
|
|
825
|
+
`RemoteMediaManager#logMainVideoReceiveSlots --> MAIN VIDEO receive slots: unused=${this.slots.video.unused.length}, activeSpeaker=${this.slots.video.activeSpeaker.length}, receiverSelected=${this.slots.video.receiverSelected.length}${logMessage}`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/** logs main audio slots */
|
|
830
|
+
private logMainAudioReceiveSlots() {
|
|
831
|
+
LoggerProxy.logger.log(
|
|
832
|
+
`RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio.main
|
|
833
|
+
.map((slot) => slot.logString)
|
|
834
|
+
.join(', ')}`
|
|
741
835
|
);
|
|
742
836
|
}
|
|
743
837
|
|
|
838
|
+
/** logs slides video slots */
|
|
839
|
+
private logSlidesVideoReceiveSlots() {
|
|
840
|
+
LoggerProxy.logger.log(
|
|
841
|
+
`RemoteMediaManager#logSlidesVideoReceiveSlots --> SLIDES VIDEO receive slot: ${this.slots.screenShare.video?.logString}`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/** logs slides audio slots */
|
|
846
|
+
private logSlidesAudioReceiveSlots() {
|
|
847
|
+
LoggerProxy.logger.log(
|
|
848
|
+
`RemoteMediaManager#logSlidesAudioReceiveSlots --> SLIDES AUDIO receive slots: ${this.slots.screenShare.audio
|
|
849
|
+
.map((slot) => slot.logString)
|
|
850
|
+
.join(', ')}`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** Logs all current receive slots */
|
|
855
|
+
public logAllReceiveSlots() {
|
|
856
|
+
this.logMainVideoReceiveSlots();
|
|
857
|
+
this.logMainAudioReceiveSlots();
|
|
858
|
+
this.logSlidesVideoReceiveSlots();
|
|
859
|
+
this.logSlidesAudioReceiveSlots();
|
|
860
|
+
}
|
|
861
|
+
|
|
744
862
|
/**
|
|
745
863
|
* Makes sure we have the right number of receive slots created for the current layout
|
|
746
864
|
* and allocates them to the right video panes / pane groups
|
|
@@ -765,7 +883,7 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
765
883
|
// allocate receiver selected
|
|
766
884
|
this.allocateSlotsToReceiverSelectedVideoPaneGroups();
|
|
767
885
|
|
|
768
|
-
this.
|
|
886
|
+
this.logMainVideoReceiveSlots();
|
|
769
887
|
|
|
770
888
|
// If this is the initial layout, there may be some "unused" slots left because of the preallocation
|
|
771
889
|
// done in this.preallocateVideoReceiveSlots(), so release them now
|
|
@@ -887,8 +1005,13 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
887
1005
|
}) {
|
|
888
1006
|
const {audio, video, screenShareAudio, screenShareVideo, commit} = options;
|
|
889
1007
|
|
|
890
|
-
if (audio
|
|
891
|
-
this.media.audio.
|
|
1008
|
+
if (audio) {
|
|
1009
|
+
if (this.media.audio.main) {
|
|
1010
|
+
this.media.audio.main.stop(commit);
|
|
1011
|
+
}
|
|
1012
|
+
if (this.media.audio.si) {
|
|
1013
|
+
this.media.audio.si.stop(commit);
|
|
1014
|
+
}
|
|
892
1015
|
}
|
|
893
1016
|
if (video) {
|
|
894
1017
|
Object.values(this.media.video.activeSpeakerGroups).forEach((remoteMediaGroup) => {
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
MultistreamRoapMediaConnection,
|
|
6
6
|
} from '@webex/internal-media-core';
|
|
7
7
|
|
|
8
|
+
import {NamedMediaGroup} from '@webex/json-multistream';
|
|
9
|
+
|
|
8
10
|
export default class SendSlotManager {
|
|
9
11
|
private readonly slots: Map<MediaType, SendSlot> = new Map();
|
|
10
12
|
private readonly LoggerProxy: any;
|
|
@@ -55,6 +57,33 @@ export default class SendSlotManager {
|
|
|
55
57
|
return slot;
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Allow users to specify 'namedMediaGroups' to indicate which named media group its audio should be sent to.
|
|
62
|
+
* @param {MediaType} mediaType MediaType of the sendSlot to which the audio stream needs to be send to the media group
|
|
63
|
+
* @param {[]}namedMediaGroups - Allow users to specify 'namedMediaGroups'.If the value of 'namedMediaGroups' is zero,
|
|
64
|
+
* named media group will be canceled and the audio stream will be sent to the floor.
|
|
65
|
+
* @returns {void}
|
|
66
|
+
*/
|
|
67
|
+
public setNamedMediaGroups(mediaType: MediaType, namedMediaGroups: NamedMediaGroup[]) {
|
|
68
|
+
if (mediaType !== MediaType.AudioMain) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`sendSlotManager cannot set named media group which media type is ${mediaType}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const slot = this.slots.get(mediaType);
|
|
75
|
+
|
|
76
|
+
if (!slot) {
|
|
77
|
+
throw new Error(`Slot for ${mediaType} does not exist`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
slot.setNamedMediaGroups(namedMediaGroups);
|
|
81
|
+
|
|
82
|
+
this.LoggerProxy.logger.info(
|
|
83
|
+
`SendSlotsManager->setNamedMediaGroups#set named media group ${namedMediaGroups}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
58
87
|
/**
|
|
59
88
|
* This method publishes the given stream to the sendSlot for the given mediaType
|
|
60
89
|
* @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {Defer} from '@webex/common';
|
|
2
|
+
|
|
3
|
+
import LoggerProxy from '../common/logs/logger-proxy';
|
|
4
|
+
import {ClusterNode} from './request';
|
|
5
|
+
import {convertStunUrlToTurn} from './util';
|
|
6
|
+
|
|
7
|
+
import {ICE_GATHERING_STATE, CONNECTION_STATE} from '../constants';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_TIMEOUT = 3000;
|
|
10
|
+
const VIDEO_MESH_TIMEOUT = 1000;
|
|
11
|
+
|
|
12
|
+
// result for a specific transport protocol (like udp or tcp)
|
|
13
|
+
export type TransportResult = {
|
|
14
|
+
result: 'reachable' | 'unreachable' | 'untested';
|
|
15
|
+
latencyInMilliseconds?: number; // amount of time it took to get the first ICE candidate
|
|
16
|
+
clientMediaIPs?: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// reachability result for a specific media cluster
|
|
20
|
+
export type ClusterReachabilityResult = {
|
|
21
|
+
udp: TransportResult;
|
|
22
|
+
tcp: TransportResult;
|
|
23
|
+
xtls: TransportResult;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A class that handles reachability checks for a single cluster.
|
|
28
|
+
*/
|
|
29
|
+
export class ClusterReachability {
|
|
30
|
+
private numUdpUrls: number;
|
|
31
|
+
private numTcpUrls: number;
|
|
32
|
+
private result: ClusterReachabilityResult;
|
|
33
|
+
private pc?: RTCPeerConnection;
|
|
34
|
+
private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
|
|
35
|
+
private startTimestamp: number;
|
|
36
|
+
public readonly isVideoMesh: boolean;
|
|
37
|
+
public readonly name;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Constructor for ClusterReachability
|
|
41
|
+
* @param {string} name cluster name
|
|
42
|
+
* @param {ClusterNode} clusterInfo information about the media cluster
|
|
43
|
+
*/
|
|
44
|
+
constructor(name: string, clusterInfo: ClusterNode) {
|
|
45
|
+
this.name = name;
|
|
46
|
+
this.isVideoMesh = clusterInfo.isVideoMesh;
|
|
47
|
+
this.numUdpUrls = clusterInfo.udp.length;
|
|
48
|
+
this.numTcpUrls = clusterInfo.tcp.length;
|
|
49
|
+
|
|
50
|
+
this.pc = this.createPeerConnection(clusterInfo);
|
|
51
|
+
|
|
52
|
+
this.defer = new Defer();
|
|
53
|
+
this.result = {
|
|
54
|
+
udp: {
|
|
55
|
+
result: 'untested',
|
|
56
|
+
},
|
|
57
|
+
tcp: {
|
|
58
|
+
result: 'untested',
|
|
59
|
+
},
|
|
60
|
+
xtls: {
|
|
61
|
+
result: 'untested',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Gets total elapsed time, can be called only after start() is called
|
|
68
|
+
* @returns {Number} Milliseconds
|
|
69
|
+
*/
|
|
70
|
+
private getElapsedTime() {
|
|
71
|
+
return Math.round(performance.now() - this.startTimestamp);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generate peerConnection config settings
|
|
76
|
+
* @param {ClusterNode} cluster
|
|
77
|
+
* @returns {RTCConfiguration} peerConnectionConfig
|
|
78
|
+
*/
|
|
79
|
+
private buildPeerConnectionConfig(cluster: ClusterNode): RTCConfiguration {
|
|
80
|
+
const udpIceServers = cluster.udp.map((url) => ({
|
|
81
|
+
username: '',
|
|
82
|
+
credential: '',
|
|
83
|
+
urls: [url],
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
// STUN servers are contacted only using UDP, so in order to test TCP reachability
|
|
87
|
+
// we pretend that Linus is a TURN server, because we can explicitly say "transport=tcp" in TURN urls.
|
|
88
|
+
// We then check for relay candidates to know if TURN-TCP worked (see registerIceCandidateListener()).
|
|
89
|
+
const tcpIceServers = cluster.tcp.map((urlString: string) => {
|
|
90
|
+
return {
|
|
91
|
+
username: 'webexturnreachuser',
|
|
92
|
+
credential: 'webexturnreachpwd',
|
|
93
|
+
urls: [convertStunUrlToTurn(urlString, 'tcp')],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
iceServers: [...udpIceServers, ...tcpIceServers],
|
|
99
|
+
iceCandidatePoolSize: 0,
|
|
100
|
+
iceTransportPolicy: 'all',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates an RTCPeerConnection
|
|
106
|
+
* @param {ClusterNode} clusterInfo information about the media cluster
|
|
107
|
+
* @returns {RTCPeerConnection} peerConnection
|
|
108
|
+
*/
|
|
109
|
+
private createPeerConnection(clusterInfo: ClusterNode) {
|
|
110
|
+
try {
|
|
111
|
+
const config = this.buildPeerConnectionConfig(clusterInfo);
|
|
112
|
+
|
|
113
|
+
const peerConnection = new RTCPeerConnection(config);
|
|
114
|
+
|
|
115
|
+
return peerConnection;
|
|
116
|
+
} catch (peerConnectionError) {
|
|
117
|
+
LoggerProxy.logger.warn(
|
|
118
|
+
`Reachability:index#createPeerConnection --> Error creating peerConnection:`,
|
|
119
|
+
peerConnectionError
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @returns {ClusterReachabilityResult} reachability result for this cluster
|
|
128
|
+
*/
|
|
129
|
+
getResult() {
|
|
130
|
+
return this.result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Closes the peerConnection
|
|
135
|
+
*
|
|
136
|
+
* @returns {void}
|
|
137
|
+
*/
|
|
138
|
+
private closePeerConnection() {
|
|
139
|
+
if (this.pc) {
|
|
140
|
+
this.pc.onicecandidate = null;
|
|
141
|
+
this.pc.onicegatheringstatechange = null;
|
|
142
|
+
this.pc.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolves the defer, indicating that reachability checks for this cluster are completed
|
|
148
|
+
*
|
|
149
|
+
* @returns {void}
|
|
150
|
+
*/
|
|
151
|
+
private finishReachabilityCheck() {
|
|
152
|
+
this.defer.resolve();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Adds public IP (client media IPs)
|
|
157
|
+
* @param {string} protocol
|
|
158
|
+
* @param {string} publicIP
|
|
159
|
+
* @returns {void}
|
|
160
|
+
*/
|
|
161
|
+
private addPublicIP(protocol: 'udp' | 'tcp', publicIP?: string | null) {
|
|
162
|
+
const result = this.result[protocol];
|
|
163
|
+
|
|
164
|
+
if (publicIP) {
|
|
165
|
+
if (result.clientMediaIPs) {
|
|
166
|
+
if (!result.clientMediaIPs.includes(publicIP)) {
|
|
167
|
+
result.clientMediaIPs.push(publicIP);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
result.clientMediaIPs = [publicIP];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Registers a listener for the iceGatheringStateChange event
|
|
177
|
+
*
|
|
178
|
+
* @returns {void}
|
|
179
|
+
*/
|
|
180
|
+
private registerIceGatheringStateChangeListener() {
|
|
181
|
+
this.pc.onicegatheringstatechange = () => {
|
|
182
|
+
const {COMPLETE} = ICE_GATHERING_STATE;
|
|
183
|
+
|
|
184
|
+
if (this.pc.iceConnectionState === COMPLETE) {
|
|
185
|
+
this.closePeerConnection();
|
|
186
|
+
this.finishReachabilityCheck();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Checks if we have the results for all the protocols (UDP and TCP)
|
|
193
|
+
*
|
|
194
|
+
* @returns {boolean} true if we have all results, false otherwise
|
|
195
|
+
*/
|
|
196
|
+
private haveWeGotAllResults(): boolean {
|
|
197
|
+
return ['udp', 'tcp'].every(
|
|
198
|
+
(protocol) =>
|
|
199
|
+
this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Stores the latency in the result for the given protocol and marks it as reachable
|
|
205
|
+
*
|
|
206
|
+
* @param {string} protocol
|
|
207
|
+
* @param {number} latency
|
|
208
|
+
* @returns {void}
|
|
209
|
+
*/
|
|
210
|
+
private storeLatencyResult(protocol: 'udp' | 'tcp', latency: number) {
|
|
211
|
+
const result = this.result[protocol];
|
|
212
|
+
|
|
213
|
+
if (result.latencyInMilliseconds === undefined) {
|
|
214
|
+
LoggerProxy.logger.log(
|
|
215
|
+
// @ts-ignore
|
|
216
|
+
`Reachability:index#storeLatencyResult --> Successfully reached ${this.name} over ${protocol}: ${latency}ms`
|
|
217
|
+
);
|
|
218
|
+
result.latencyInMilliseconds = latency;
|
|
219
|
+
result.result = 'reachable';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Registers a listener for the icecandidate event
|
|
225
|
+
*
|
|
226
|
+
* @returns {void}
|
|
227
|
+
*/
|
|
228
|
+
private registerIceCandidateListener() {
|
|
229
|
+
this.pc.onicecandidate = (e) => {
|
|
230
|
+
const CANDIDATE_TYPES = {
|
|
231
|
+
SERVER_REFLEXIVE: 'srflx',
|
|
232
|
+
RELAY: 'relay',
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
if (e.candidate) {
|
|
236
|
+
if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
|
|
237
|
+
this.storeLatencyResult('udp', this.getElapsedTime());
|
|
238
|
+
this.addPublicIP('udp', e.candidate.address);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
|
|
242
|
+
this.storeLatencyResult('tcp', this.getElapsedTime());
|
|
243
|
+
// we don't add public IP for TCP, because in the case of relay candidates
|
|
244
|
+
// e.candidate.address is the TURN server address, not the client's public IP
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (this.haveWeGotAllResults()) {
|
|
248
|
+
this.closePeerConnection();
|
|
249
|
+
this.finishReachabilityCheck();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Starts the process of doing UDP and TCP reachability checks on the media cluster.
|
|
257
|
+
* XTLS reachability checking is not supported.
|
|
258
|
+
*
|
|
259
|
+
* @returns {Promise}
|
|
260
|
+
*/
|
|
261
|
+
async start(): Promise<ClusterReachabilityResult> {
|
|
262
|
+
if (!this.pc) {
|
|
263
|
+
LoggerProxy.logger.warn(
|
|
264
|
+
`Reachability:ClusterReachability#start --> Error: peerConnection is undefined`
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return this.result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Initialize this.result as saying that nothing is reachable.
|
|
271
|
+
// It will get updated as we go along and successfully gather ICE candidates.
|
|
272
|
+
this.result.udp = {
|
|
273
|
+
result: this.numUdpUrls > 0 ? 'unreachable' : 'untested',
|
|
274
|
+
};
|
|
275
|
+
this.result.tcp = {
|
|
276
|
+
result: this.numTcpUrls > 0 ? 'unreachable' : 'untested',
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const offer = await this.pc.createOffer({offerToReceiveAudio: true});
|
|
281
|
+
|
|
282
|
+
this.startTimestamp = performance.now();
|
|
283
|
+
|
|
284
|
+
// not awaiting the next call on purpose, because we're not sending the offer anywhere and there won't be any answer
|
|
285
|
+
// we just need to make this call to trigger the ICE gathering process
|
|
286
|
+
this.pc.setLocalDescription(offer);
|
|
287
|
+
|
|
288
|
+
await this.gatherIceCandidates();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
LoggerProxy.logger.warn(`Reachability:ClusterReachability#start --> Error: `, error);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return this.result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Starts the process of gathering ICE candidates
|
|
298
|
+
*
|
|
299
|
+
* @returns {Promise} promise that's resolved once reachability checks for this cluster are completed or timeout is reached
|
|
300
|
+
*/
|
|
301
|
+
private gatherIceCandidates() {
|
|
302
|
+
const timeout = this.isVideoMesh ? VIDEO_MESH_TIMEOUT : DEFAULT_TIMEOUT;
|
|
303
|
+
|
|
304
|
+
this.registerIceGatheringStateChangeListener();
|
|
305
|
+
this.registerIceCandidateListener();
|
|
306
|
+
|
|
307
|
+
// Set maximum timeout
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
const {CLOSED} = CONNECTION_STATE;
|
|
310
|
+
|
|
311
|
+
// Close any open peerConnections
|
|
312
|
+
if (this.pc.connectionState !== CLOSED) {
|
|
313
|
+
this.closePeerConnection();
|
|
314
|
+
this.finishReachabilityCheck();
|
|
315
|
+
}
|
|
316
|
+
}, timeout);
|
|
317
|
+
|
|
318
|
+
return this.defer.promise;
|
|
319
|
+
}
|
|
320
|
+
}
|