@webex/plugin-meetings 3.0.0 → 3.1.0-next.2
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/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +8 -4
- package/dist/constants.js.map +1 -1
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -1
- 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/mediaSharesUtils.js +15 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/selfUtils.js +5 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +163 -0
- package/dist/media/MediaConnectionAwaiter.js.map +1 -0
- package/dist/media/index.js +4 -1
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +4 -24
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +893 -677
- 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 +32 -23
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/util.js +304 -267
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +334 -295
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/index.js +20 -0
- package/dist/meetings/index.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 +179 -65
- 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 +29 -15
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +18 -2
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +12 -10
- package/dist/reachability/request.js.map +1 -1
- package/dist/reachability/util.js +19 -0
- package/dist/reachability/util.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +15 -0
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +3 -3
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +307 -126
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/index.js +53 -30
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/{config.d.ts → types/config.d.ts} +1 -0
- package/dist/{constants.d.ts → types/constants.d.ts} +5 -4
- package/dist/types/index.d.ts +19 -0
- package/dist/types/media/MediaConnectionAwaiter.d.ts +61 -0
- package/dist/{meeting → types/meeting}/index.d.ts +26 -7
- package/dist/{meeting → types/meeting}/muteState.d.ts +2 -8
- package/dist/{meeting → types/meeting}/request.d.ts +3 -0
- package/dist/{meeting-info → types/meeting-info}/index.d.ts +1 -1
- package/dist/{meeting-info → types/meeting-info}/meeting-info-v2.d.ts +1 -1
- package/dist/types/meeting-info/util.d.ts +49 -0
- package/dist/types/meeting-info/utilv2.d.ts +65 -0
- package/dist/{meetings → types/meetings}/index.d.ts +8 -0
- package/dist/{multistream → types/multistream}/mediaRequestManager.d.ts +2 -1
- package/dist/{multistream → types/multistream}/remoteMediaGroup.d.ts +2 -0
- package/dist/{multistream → types/multistream}/remoteMediaManager.d.ts +15 -0
- package/dist/{multistream → types/multistream}/sendSlotManager.d.ts +9 -1
- package/dist/{reachability → types/reachability}/clusterReachability.d.ts +1 -0
- package/dist/{reachability → types/reachability}/index.d.ts +4 -0
- package/dist/{reachability → types/reachability}/util.d.ts +7 -0
- package/dist/{roap → types/roap}/index.d.ts +10 -2
- package/dist/{roap → types/roap}/turnDiscovery.d.ts +64 -17
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -23
- package/src/config.ts +1 -0
- package/src/constants.ts +7 -3
- package/src/index.ts +31 -0
- package/src/interpretation/index.ts +18 -1
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/locus-info/selfUtils.ts +5 -0
- package/src/media/MediaConnectionAwaiter.ts +174 -0
- package/src/media/index.ts +3 -1
- package/src/media/properties.ts +6 -31
- package/src/meeting/index.ts +321 -106
- package/src/meeting/muteState.ts +34 -20
- package/src/meeting/request.ts +18 -2
- package/src/meeting/util.ts +1 -0
- package/src/meeting-info/util.ts +241 -233
- package/src/meeting-info/utilv2.ts +250 -243
- package/src/meetings/index.ts +18 -0
- package/src/multistream/mediaRequestManager.ts +4 -1
- package/src/multistream/remoteMediaGroup.ts +19 -0
- package/src/multistream/remoteMediaManager.ts +101 -16
- package/src/multistream/sendSlotManager.ts +28 -0
- package/src/reachability/clusterReachability.ts +20 -5
- package/src/reachability/index.ts +24 -1
- package/src/reachability/request.ts +15 -11
- package/src/reachability/util.ts +21 -0
- package/src/reconnection-manager/index.ts +1 -1
- package/src/roap/index.ts +25 -3
- package/src/roap/request.ts +3 -3
- package/src/roap/turnDiscovery.ts +244 -78
- package/src/statsAnalyzer/index.ts +63 -27
- package/test/integration/spec/journey.js +14 -14
- package/test/integration/spec/space-meeting.js +1 -1
- package/test/unit/spec/interpretation/index.ts +39 -3
- package/test/unit/spec/locus-info/index.js +28 -19
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
- package/test/unit/spec/locus-info/selfUtils.js +42 -12
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +344 -0
- package/test/unit/spec/media/index.ts +89 -78
- package/test/unit/spec/media/properties.ts +16 -70
- package/test/unit/spec/meeting/index.js +638 -139
- package/test/unit/spec/meeting/muteState.js +219 -67
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +6 -1
- package/test/unit/spec/meeting-info/utilv2.js +6 -0
- package/test/unit/spec/meetings/index.js +40 -20
- package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
- package/test/unit/spec/multistream/remoteMediaGroup.ts +79 -1
- package/test/unit/spec/multistream/remoteMediaManager.ts +199 -1
- package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
- package/test/unit/spec/reachability/clusterReachability.ts +86 -22
- package/test/unit/spec/reachability/index.ts +197 -60
- package/test/unit/spec/reachability/request.js +15 -7
- package/test/unit/spec/reachability/util.ts +32 -2
- package/test/unit/spec/reconnection-manager/index.js +28 -0
- package/test/unit/spec/roap/index.ts +61 -6
- package/test/unit/spec/roap/turnDiscovery.ts +298 -16
- package/test/unit/spec/stats-analyzer/index.js +179 -0
- package/dist/index.d.ts +0 -7
- package/dist/meeting-info/util.d.ts +0 -2
- package/dist/meeting-info/utilv2.d.ts +0 -2
- package/dist/member/member.types.d.ts +0 -11
- package/dist/member/member.types.js +0 -17
- package/dist/member/member.types.js.map +0 -1
- package/src/member/member.types.ts +0 -13
- /package/dist/{annotation → types/annotation}/annotation.types.d.ts +0 -0
- /package/dist/{annotation → types/annotation}/constants.d.ts +0 -0
- /package/dist/{annotation → types/annotation}/index.d.ts +0 -0
- /package/dist/{breakouts → types/breakouts}/breakout.d.ts +0 -0
- /package/dist/{breakouts → types/breakouts}/collection.d.ts +0 -0
- /package/dist/{breakouts → types/breakouts}/edit-lock-error.d.ts +0 -0
- /package/dist/{breakouts → types/breakouts}/events.d.ts +0 -0
- /package/dist/{breakouts → types/breakouts}/index.d.ts +0 -0
- /package/dist/{breakouts → types/breakouts}/request.d.ts +0 -0
- /package/dist/{breakouts → types/breakouts}/utils.d.ts +0 -0
- /package/dist/{common → types/common}/browser-detection.d.ts +0 -0
- /package/dist/{common → types/common}/collection.d.ts +0 -0
- /package/dist/{common → types/common}/config.d.ts +0 -0
- /package/dist/{common → types/common}/errors/captcha-error.d.ts +0 -0
- /package/dist/{common → types/common}/errors/intent-to-join.d.ts +0 -0
- /package/dist/{common → types/common}/errors/join-meeting.d.ts +0 -0
- /package/dist/{common → types/common}/errors/media.d.ts +0 -0
- /package/dist/{common → types/common}/errors/no-meeting-info.d.ts +0 -0
- /package/dist/{common → types/common}/errors/parameter.d.ts +0 -0
- /package/dist/{common → types/common}/errors/password-error.d.ts +0 -0
- /package/dist/{common → types/common}/errors/permission.d.ts +0 -0
- /package/dist/{common → types/common}/errors/reclaim-host-role-errors.d.ts +0 -0
- /package/dist/{common → types/common}/errors/reconnection-in-progress.d.ts +0 -0
- /package/dist/{common → types/common}/errors/reconnection.d.ts +0 -0
- /package/dist/{common → types/common}/errors/stats.d.ts +0 -0
- /package/dist/{common → types/common}/errors/webex-errors.d.ts +0 -0
- /package/dist/{common → types/common}/errors/webex-meetings-error.d.ts +0 -0
- /package/dist/{common → types/common}/events/events-scope.d.ts +0 -0
- /package/dist/{common → types/common}/events/events.d.ts +0 -0
- /package/dist/{common → types/common}/events/trigger-proxy.d.ts +0 -0
- /package/dist/{common → types/common}/events/util.d.ts +0 -0
- /package/dist/{common → types/common}/logs/logger-config.d.ts +0 -0
- /package/dist/{common → types/common}/logs/logger-proxy.d.ts +0 -0
- /package/dist/{common → types/common}/logs/request.d.ts +0 -0
- /package/dist/{common → types/common}/queue.d.ts +0 -0
- /package/dist/{controls-options-manager → types/controls-options-manager}/constants.d.ts +0 -0
- /package/dist/{controls-options-manager → types/controls-options-manager}/enums.d.ts +0 -0
- /package/dist/{controls-options-manager → types/controls-options-manager}/index.d.ts +0 -0
- /package/dist/{controls-options-manager → types/controls-options-manager}/types.d.ts +0 -0
- /package/dist/{controls-options-manager → types/controls-options-manager}/util.d.ts +0 -0
- /package/dist/{interceptors → types/interceptors}/index.d.ts +0 -0
- /package/dist/{interceptors → types/interceptors}/locusRetry.d.ts +0 -0
- /package/dist/{interpretation → types/interpretation}/collection.d.ts +0 -0
- /package/dist/{interpretation → types/interpretation}/index.d.ts +0 -0
- /package/dist/{interpretation → types/interpretation}/siLanguage.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/controlsUtils.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/embeddedAppsUtils.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/fullState.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/hostUtils.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/index.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/infoUtils.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/mediaSharesUtils.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/parser.d.ts +0 -0
- /package/dist/{locus-info → types/locus-info}/selfUtils.d.ts +0 -0
- /package/dist/{media → types/media}/index.d.ts +0 -0
- /package/dist/{media → types/media}/properties.d.ts +0 -0
- /package/dist/{media → types/media}/util.d.ts +0 -0
- /package/dist/{mediaQualityMetrics → types/mediaQualityMetrics}/config.d.ts +0 -0
- /package/dist/{meeting → types/meeting}/in-meeting-actions.d.ts +0 -0
- /package/dist/{meeting → types/meeting}/locusMediaRequest.d.ts +0 -0
- /package/dist/{meeting → types/meeting}/request.type.d.ts +0 -0
- /package/dist/{meeting → types/meeting}/state.d.ts +0 -0
- /package/dist/{meeting → types/meeting}/util.d.ts +0 -0
- /package/dist/{meeting → types/meeting}/voicea-meeting.d.ts +0 -0
- /package/dist/{meeting-info → types/meeting-info}/collection.d.ts +0 -0
- /package/dist/{meeting-info → types/meeting-info}/request.d.ts +0 -0
- /package/dist/{meetings → types/meetings}/collection.d.ts +0 -0
- /package/dist/{meetings → types/meetings}/meetings.types.d.ts +0 -0
- /package/dist/{meetings → types/meetings}/request.d.ts +0 -0
- /package/dist/{meetings → types/meetings}/util.d.ts +0 -0
- /package/dist/{member → types/member}/index.d.ts +0 -0
- /package/dist/{member → types/member}/types.d.ts +0 -0
- /package/dist/{member → types/member}/util.d.ts +0 -0
- /package/dist/{members → types/members}/collection.d.ts +0 -0
- /package/dist/{members → types/members}/index.d.ts +0 -0
- /package/dist/{members → types/members}/request.d.ts +0 -0
- /package/dist/{members → types/members}/types.d.ts +0 -0
- /package/dist/{members → types/members}/util.d.ts +0 -0
- /package/dist/{metrics → types/metrics}/constants.d.ts +0 -0
- /package/dist/{metrics → types/metrics}/index.d.ts +0 -0
- /package/dist/{multistream → types/multistream}/receiveSlot.d.ts +0 -0
- /package/dist/{multistream → types/multistream}/receiveSlotManager.d.ts +0 -0
- /package/dist/{multistream → types/multistream}/remoteMedia.d.ts +0 -0
- /package/dist/{networkQualityMonitor → types/networkQualityMonitor}/index.d.ts +0 -0
- /package/dist/{personal-meeting-room → types/personal-meeting-room}/index.d.ts +0 -0
- /package/dist/{personal-meeting-room → types/personal-meeting-room}/request.d.ts +0 -0
- /package/dist/{personal-meeting-room → types/personal-meeting-room}/util.d.ts +0 -0
- /package/dist/{reachability → types/reachability}/request.d.ts +0 -0
- /package/dist/{reactions → types/reactions}/constants.d.ts +0 -0
- /package/dist/{reactions → types/reactions}/reactions.d.ts +0 -0
- /package/dist/{reactions → types/reactions}/reactions.type.d.ts +0 -0
- /package/dist/{reconnection-manager → types/reconnection-manager}/index.d.ts +0 -0
- /package/dist/{recording-controller → types/recording-controller}/enums.d.ts +0 -0
- /package/dist/{recording-controller → types/recording-controller}/index.d.ts +0 -0
- /package/dist/{recording-controller → types/recording-controller}/util.d.ts +0 -0
- /package/dist/{roap → types/roap}/request.d.ts +0 -0
- /package/dist/{rtcMetrics → types/rtcMetrics}/constants.d.ts +0 -0
- /package/dist/{rtcMetrics → types/rtcMetrics}/index.d.ts +0 -0
- /package/dist/{statsAnalyzer → types/statsAnalyzer}/global.d.ts +0 -0
- /package/dist/{statsAnalyzer → types/statsAnalyzer}/index.d.ts +0 -0
- /package/dist/{statsAnalyzer → types/statsAnalyzer}/mqaUtil.d.ts +0 -0
- /package/dist/{transcription → types/transcription}/index.d.ts +0 -0
- /package/dist/{webinar → types/webinar}/collection.d.ts +0 -0
- /package/dist/{webinar → types/webinar}/index.d.ts +0 -0
- /package/test/unit/spec/locus-info/{lib/selfConstant.js → selfConstant.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable valid-jsdoc */
|
|
2
2
|
import {cloneDeep, forEach, remove} from 'lodash';
|
|
3
3
|
import {EventMap} from 'typed-emitter';
|
|
4
|
-
import {MediaType} from '@webex/internal-media-core';
|
|
4
|
+
import {MediaType, NamedMediaGroup} from '@webex/internal-media-core';
|
|
5
5
|
|
|
6
6
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
7
7
|
import EventsScope from '../common/events/events-scope';
|
|
@@ -11,6 +11,7 @@ import {ReceiveSlot, CSI} from './receiveSlot';
|
|
|
11
11
|
import {ReceiveSlotManager} from './receiveSlotManager';
|
|
12
12
|
import {RemoteMediaGroup} from './remoteMediaGroup';
|
|
13
13
|
import {MediaRequestManager} from './mediaRequestManager';
|
|
14
|
+
import {NAMED_MEDIA_GROUP_TYPE_AUDIO} from '../constants';
|
|
14
15
|
|
|
15
16
|
export type PaneSize = RemoteVideoResolution;
|
|
16
17
|
export type LayoutId = string;
|
|
@@ -49,6 +50,7 @@ export interface Configuration {
|
|
|
49
50
|
|
|
50
51
|
layouts: {[key: LayoutId]: VideoLayout}; // a map of all available layouts, a layout can be set via setLayout() method
|
|
51
52
|
};
|
|
53
|
+
namedMediaGroup?: NamedMediaGroup;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/* Predefined layouts: */
|
|
@@ -173,6 +175,7 @@ export const DefaultConfiguration: Configuration = {
|
|
|
173
175
|
export enum Event {
|
|
174
176
|
// events for audio streams
|
|
175
177
|
AudioCreated = 'AudioCreated',
|
|
178
|
+
InterpretationAudioCreated = 'InterpretationAudioCreated',
|
|
176
179
|
ScreenShareAudioCreated = 'ScreenShareAudioCreated',
|
|
177
180
|
|
|
178
181
|
// events for video streams
|
|
@@ -221,7 +224,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
221
224
|
private currentLayout?: VideoLayout;
|
|
222
225
|
|
|
223
226
|
private slots: {
|
|
224
|
-
audio:
|
|
227
|
+
audio: {
|
|
228
|
+
main: ReceiveSlot[];
|
|
229
|
+
si: ReceiveSlot;
|
|
230
|
+
};
|
|
225
231
|
screenShare: {
|
|
226
232
|
audio: ReceiveSlot[];
|
|
227
233
|
video?: ReceiveSlot;
|
|
@@ -234,7 +240,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
234
240
|
};
|
|
235
241
|
|
|
236
242
|
private media: {
|
|
237
|
-
audio
|
|
243
|
+
audio: {
|
|
244
|
+
main?: RemoteMediaGroup;
|
|
245
|
+
si?: RemoteMediaGroup;
|
|
246
|
+
};
|
|
238
247
|
video: {
|
|
239
248
|
activeSpeakerGroups: {
|
|
240
249
|
[key: PaneGroupId]: RemoteMediaGroup;
|
|
@@ -277,7 +286,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
277
286
|
this.receiveSlotManager = receiveSlotManager;
|
|
278
287
|
this.mediaRequestManagers = mediaRequestManagers;
|
|
279
288
|
this.media = {
|
|
280
|
-
audio:
|
|
289
|
+
audio: {
|
|
290
|
+
main: undefined,
|
|
291
|
+
si: undefined,
|
|
292
|
+
},
|
|
281
293
|
video: {
|
|
282
294
|
activeSpeakerGroups: {},
|
|
283
295
|
memberPanes: {},
|
|
@@ -291,7 +303,10 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
291
303
|
this.checkConfigValidity();
|
|
292
304
|
|
|
293
305
|
this.slots = {
|
|
294
|
-
audio:
|
|
306
|
+
audio: {
|
|
307
|
+
main: [],
|
|
308
|
+
si: undefined,
|
|
309
|
+
},
|
|
295
310
|
screenShare: {
|
|
296
311
|
audio: [],
|
|
297
312
|
video: undefined,
|
|
@@ -389,8 +404,11 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
389
404
|
});
|
|
390
405
|
|
|
391
406
|
// release all audio receive slots
|
|
392
|
-
this.slots.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
393
|
-
this.slots.audio.length = 0;
|
|
407
|
+
this.slots.audio.main.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
408
|
+
this.slots.audio.main.length = 0;
|
|
409
|
+
if (this.slots.audio.si) {
|
|
410
|
+
this.receiveSlotManager.releaseSlot(this.slots.audio.si);
|
|
411
|
+
}
|
|
394
412
|
|
|
395
413
|
// release screen share slots
|
|
396
414
|
this.slots.screenShare.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
|
|
@@ -525,22 +543,54 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
525
543
|
this.mediaRequestManagers.video.commit();
|
|
526
544
|
}
|
|
527
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Sets which named media group need receiving
|
|
548
|
+
* @param {MediaType} mediaType of the stream
|
|
549
|
+
* @param {number} languageCode of the stream. If the languageId is 0, the named media group request will be canceled,
|
|
550
|
+
* and only receive the main audio stream.
|
|
551
|
+
* @returns {void}
|
|
552
|
+
*/
|
|
553
|
+
public async setReceiveNamedMediaGroup(mediaType: MediaType, languageId: number) {
|
|
554
|
+
if (mediaType !== MediaType.AudioMain) {
|
|
555
|
+
throw new Error(`cannot set receive named media group which media type is ${mediaType}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const value = languageId;
|
|
559
|
+
if (value === this.config.namedMediaGroup?.value) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
this.config.namedMediaGroup = {
|
|
564
|
+
type: NAMED_MEDIA_GROUP_TYPE_AUDIO,
|
|
565
|
+
value,
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
if (!this.media.audio.si) {
|
|
569
|
+
await this.createInterpretationAudioMedia(true);
|
|
570
|
+
} else {
|
|
571
|
+
this.media.audio.si.setNamedMediaGroup(this.config.namedMediaGroup, true);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
528
575
|
/**
|
|
529
576
|
* Creates the audio slots
|
|
530
577
|
*/
|
|
531
578
|
private async createAudioMedia() {
|
|
532
|
-
// create
|
|
579
|
+
// create si audio request
|
|
580
|
+
await this.createInterpretationAudioMedia(false);
|
|
581
|
+
|
|
582
|
+
// create main audio receive slots
|
|
533
583
|
for (let i = 0; i < this.config.audio.numOfActiveSpeakerStreams; i += 1) {
|
|
534
584
|
// eslint-disable-next-line no-await-in-loop
|
|
535
585
|
const slot = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
|
|
536
586
|
|
|
537
|
-
this.slots.audio.push(slot);
|
|
587
|
+
this.slots.audio.main.push(slot);
|
|
538
588
|
}
|
|
539
589
|
|
|
540
|
-
// create a remote media group
|
|
541
|
-
this.media.audio = new RemoteMediaGroup(
|
|
590
|
+
// create a remote media group for main audio
|
|
591
|
+
this.media.audio.main = new RemoteMediaGroup(
|
|
542
592
|
this.mediaRequestManagers.audio,
|
|
543
|
-
this.slots.audio,
|
|
593
|
+
this.slots.audio.main,
|
|
544
594
|
255,
|
|
545
595
|
true
|
|
546
596
|
);
|
|
@@ -548,10 +598,40 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
548
598
|
this.emit(
|
|
549
599
|
{file: 'multistream/remoteMediaManager', function: 'createAudioMedia'},
|
|
550
600
|
Event.AudioCreated,
|
|
551
|
-
this.media.audio
|
|
601
|
+
this.media.audio.main
|
|
552
602
|
);
|
|
553
603
|
}
|
|
554
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Creates the audio slots for named media
|
|
607
|
+
*/
|
|
608
|
+
private async createInterpretationAudioMedia(commitRequest: boolean) {
|
|
609
|
+
// create slot for interpretation language audio
|
|
610
|
+
if (
|
|
611
|
+
this.config.namedMediaGroup?.type === NAMED_MEDIA_GROUP_TYPE_AUDIO &&
|
|
612
|
+
this.config.namedMediaGroup?.value
|
|
613
|
+
) {
|
|
614
|
+
this.slots.audio.si = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
|
|
615
|
+
|
|
616
|
+
// create a remote media group for si audio
|
|
617
|
+
this.media.audio.si = new RemoteMediaGroup(
|
|
618
|
+
this.mediaRequestManagers.audio,
|
|
619
|
+
[this.slots.audio.si],
|
|
620
|
+
255,
|
|
621
|
+
commitRequest,
|
|
622
|
+
{
|
|
623
|
+
namedMediaGroup: this.config.namedMediaGroup,
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
this.emit(
|
|
628
|
+
{file: 'multistream/remoteMediaManager', function: 'createInterpretationAudioMedia'},
|
|
629
|
+
Event.InterpretationAudioCreated,
|
|
630
|
+
this.media.audio.si
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
555
635
|
/**
|
|
556
636
|
* Creates receive slots required for receiving screen share audio and video
|
|
557
637
|
*/
|
|
@@ -748,7 +828,7 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
748
828
|
/** logs main audio slots */
|
|
749
829
|
private logMainAudioReceiveSlots() {
|
|
750
830
|
LoggerProxy.logger.log(
|
|
751
|
-
`RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio
|
|
831
|
+
`RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio.main
|
|
752
832
|
.map((slot) => slot.logString)
|
|
753
833
|
.join(', ')}`
|
|
754
834
|
);
|
|
@@ -924,8 +1004,13 @@ export class RemoteMediaManager extends EventsScope {
|
|
|
924
1004
|
}) {
|
|
925
1005
|
const {audio, video, screenShareAudio, screenShareVideo, commit} = options;
|
|
926
1006
|
|
|
927
|
-
if (audio
|
|
928
|
-
this.media.audio.
|
|
1007
|
+
if (audio) {
|
|
1008
|
+
if (this.media.audio.main) {
|
|
1009
|
+
this.media.audio.main.stop(commit);
|
|
1010
|
+
}
|
|
1011
|
+
if (this.media.audio.si) {
|
|
1012
|
+
this.media.audio.si.stop(commit);
|
|
1013
|
+
}
|
|
929
1014
|
}
|
|
930
1015
|
if (video) {
|
|
931
1016
|
Object.values(this.media.video.activeSpeakerGroups).forEach((remoteMediaGroup) => {
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
MediaType,
|
|
4
4
|
LocalStream,
|
|
5
5
|
MultistreamRoapMediaConnection,
|
|
6
|
+
NamedMediaGroup,
|
|
6
7
|
} from '@webex/internal-media-core';
|
|
7
8
|
|
|
8
9
|
export default class SendSlotManager {
|
|
@@ -55,6 +56,33 @@ export default class SendSlotManager {
|
|
|
55
56
|
return slot;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Allow users to specify 'namedMediaGroups' to indicate which named media group its audio should be sent to.
|
|
61
|
+
* @param {MediaType} mediaType MediaType of the sendSlot to which the audio stream needs to be send to the media group
|
|
62
|
+
* @param {[]}namedMediaGroups - Allow users to specify 'namedMediaGroups'.If the value of 'namedMediaGroups' is zero,
|
|
63
|
+
* named media group will be canceled and the audio stream will be sent to the floor.
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
public setNamedMediaGroups(mediaType: MediaType, namedMediaGroups: NamedMediaGroup[]) {
|
|
67
|
+
if (mediaType !== MediaType.AudioMain) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`sendSlotManager cannot set named media group which media type is ${mediaType}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const slot = this.slots.get(mediaType);
|
|
74
|
+
|
|
75
|
+
if (!slot) {
|
|
76
|
+
throw new Error(`Slot for ${mediaType} does not exist`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
slot.setNamedMediaGroups(namedMediaGroups);
|
|
80
|
+
|
|
81
|
+
this.LoggerProxy.logger.info(
|
|
82
|
+
`SendSlotsManager->setNamedMediaGroups#set named media group ${namedMediaGroups}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
58
86
|
/**
|
|
59
87
|
* This method publishes the given stream to the sendSlot for the given mediaType
|
|
60
88
|
* @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
|
|
@@ -2,7 +2,7 @@ import {Defer} from '@webex/common';
|
|
|
2
2
|
|
|
3
3
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
4
4
|
import {ClusterNode} from './request';
|
|
5
|
-
import {convertStunUrlToTurn} from './util';
|
|
5
|
+
import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';
|
|
6
6
|
|
|
7
7
|
import {ICE_GATHERING_STATE, CONNECTION_STATE} from '../constants';
|
|
8
8
|
|
|
@@ -29,6 +29,7 @@ export type ClusterReachabilityResult = {
|
|
|
29
29
|
export class ClusterReachability {
|
|
30
30
|
private numUdpUrls: number;
|
|
31
31
|
private numTcpUrls: number;
|
|
32
|
+
private numXTlsUrls: number;
|
|
32
33
|
private result: ClusterReachabilityResult;
|
|
33
34
|
private pc?: RTCPeerConnection;
|
|
34
35
|
private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
|
|
@@ -46,6 +47,7 @@ export class ClusterReachability {
|
|
|
46
47
|
this.isVideoMesh = clusterInfo.isVideoMesh;
|
|
47
48
|
this.numUdpUrls = clusterInfo.udp.length;
|
|
48
49
|
this.numTcpUrls = clusterInfo.tcp.length;
|
|
50
|
+
this.numXTlsUrls = clusterInfo.xtls.length;
|
|
49
51
|
|
|
50
52
|
this.pc = this.createPeerConnection(clusterInfo);
|
|
51
53
|
|
|
@@ -94,8 +96,16 @@ export class ClusterReachability {
|
|
|
94
96
|
};
|
|
95
97
|
});
|
|
96
98
|
|
|
99
|
+
const turnTlsIceServers = cluster.xtls.map((urlString: string) => {
|
|
100
|
+
return {
|
|
101
|
+
username: 'webexturnreachuser',
|
|
102
|
+
credential: 'webexturnreachpwd',
|
|
103
|
+
urls: [convertStunUrlToTurnTls(urlString)],
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
97
107
|
return {
|
|
98
|
-
iceServers: [...udpIceServers, ...tcpIceServers],
|
|
108
|
+
iceServers: [...udpIceServers, ...tcpIceServers, ...turnTlsIceServers],
|
|
99
109
|
iceCandidatePoolSize: 0,
|
|
100
110
|
iceTransportPolicy: 'all',
|
|
101
111
|
};
|
|
@@ -194,7 +204,7 @@ export class ClusterReachability {
|
|
|
194
204
|
* @returns {boolean} true if we have all results, false otherwise
|
|
195
205
|
*/
|
|
196
206
|
private haveWeGotAllResults(): boolean {
|
|
197
|
-
return ['udp', 'tcp'].every(
|
|
207
|
+
return ['udp', 'tcp', 'xtls'].every(
|
|
198
208
|
(protocol) =>
|
|
199
209
|
this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
|
|
200
210
|
);
|
|
@@ -207,7 +217,7 @@ export class ClusterReachability {
|
|
|
207
217
|
* @param {number} latency
|
|
208
218
|
* @returns {void}
|
|
209
219
|
*/
|
|
210
|
-
private storeLatencyResult(protocol: 'udp' | 'tcp', latency: number) {
|
|
220
|
+
private storeLatencyResult(protocol: 'udp' | 'tcp' | 'xtls', latency: number) {
|
|
211
221
|
const result = this.result[protocol];
|
|
212
222
|
|
|
213
223
|
if (result.latencyInMilliseconds === undefined) {
|
|
@@ -227,6 +237,7 @@ export class ClusterReachability {
|
|
|
227
237
|
*/
|
|
228
238
|
private registerIceCandidateListener() {
|
|
229
239
|
this.pc.onicecandidate = (e) => {
|
|
240
|
+
const TURN_TLS_PORT = 443;
|
|
230
241
|
const CANDIDATE_TYPES = {
|
|
231
242
|
SERVER_REFLEXIVE: 'srflx',
|
|
232
243
|
RELAY: 'relay',
|
|
@@ -239,7 +250,8 @@ export class ClusterReachability {
|
|
|
239
250
|
}
|
|
240
251
|
|
|
241
252
|
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
|
|
242
|
-
|
|
253
|
+
const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
|
|
254
|
+
this.storeLatencyResult(protocol, this.getElapsedTime());
|
|
243
255
|
// we don't add public IP for TCP, because in the case of relay candidates
|
|
244
256
|
// e.candidate.address is the TURN server address, not the client's public IP
|
|
245
257
|
}
|
|
@@ -275,6 +287,9 @@ export class ClusterReachability {
|
|
|
275
287
|
this.result.tcp = {
|
|
276
288
|
result: this.numTcpUrls > 0 ? 'unreachable' : 'untested',
|
|
277
289
|
};
|
|
290
|
+
this.result.xtls = {
|
|
291
|
+
result: this.numXTlsUrls > 0 ? 'unreachable' : 'untested',
|
|
292
|
+
};
|
|
278
293
|
|
|
279
294
|
try {
|
|
280
295
|
const offer = await this.pc.createOffer({offerToReceiveAudio: true});
|
|
@@ -22,10 +22,14 @@ export type ReachabilityMetrics = {
|
|
|
22
22
|
reachability_public_udp_failed: number;
|
|
23
23
|
reachability_public_tcp_success: number;
|
|
24
24
|
reachability_public_tcp_failed: number;
|
|
25
|
+
reachability_public_xtls_success: number;
|
|
26
|
+
reachability_public_xtls_failed: number;
|
|
25
27
|
reachability_vmn_udp_success: number;
|
|
26
28
|
reachability_vmn_udp_failed: number;
|
|
27
29
|
reachability_vmn_tcp_success: number;
|
|
28
30
|
reachability_vmn_tcp_failed: number;
|
|
31
|
+
reachability_vmn_xtls_success: number;
|
|
32
|
+
reachability_vmn_xtls_failed: number;
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
/**
|
|
@@ -141,10 +145,14 @@ export default class Reachability {
|
|
|
141
145
|
reachability_public_udp_failed: 0,
|
|
142
146
|
reachability_public_tcp_success: 0,
|
|
143
147
|
reachability_public_tcp_failed: 0,
|
|
148
|
+
reachability_public_xtls_success: 0,
|
|
149
|
+
reachability_public_xtls_failed: 0,
|
|
144
150
|
reachability_vmn_udp_success: 0,
|
|
145
151
|
reachability_vmn_udp_failed: 0,
|
|
146
152
|
reachability_vmn_tcp_success: 0,
|
|
147
153
|
reachability_vmn_tcp_failed: 0,
|
|
154
|
+
reachability_vmn_xtls_success: 0,
|
|
155
|
+
reachability_vmn_xtls_failed: 0,
|
|
148
156
|
};
|
|
149
157
|
|
|
150
158
|
const updateStats = (clusterType: 'public' | 'vmn', result: ClusterReachabilityResult) => {
|
|
@@ -156,6 +164,10 @@ export default class Reachability {
|
|
|
156
164
|
const outcome = result.tcp.result === 'reachable' ? 'success' : 'failed';
|
|
157
165
|
stats[`reachability_${clusterType}_tcp_${outcome}`] += 1;
|
|
158
166
|
}
|
|
167
|
+
if (result.xtls && result.xtls.result !== 'untested') {
|
|
168
|
+
const outcome = result.xtls.result === 'reachable' ? 'success' : 'failed';
|
|
169
|
+
stats[`reachability_${clusterType}_xtls_${outcome}`] += 1;
|
|
170
|
+
}
|
|
159
171
|
};
|
|
160
172
|
|
|
161
173
|
try {
|
|
@@ -338,7 +350,10 @@ export default class Reachability {
|
|
|
338
350
|
LoggerProxy.logger.log(
|
|
339
351
|
`Reachability:index#performReachabilityChecks --> doing UDP${
|
|
340
352
|
// @ts-ignore
|
|
341
|
-
this.webex.config.meetings.experimental.enableTcpReachability ? '
|
|
353
|
+
this.webex.config.meetings.experimental.enableTcpReachability ? ',TCP' : ''
|
|
354
|
+
}${
|
|
355
|
+
// @ts-ignore
|
|
356
|
+
this.webex.config.meetings.experimental.enableTlsReachability ? ',TLS' : ''
|
|
342
357
|
} reachability checks`
|
|
343
358
|
);
|
|
344
359
|
|
|
@@ -354,6 +369,14 @@ export default class Reachability {
|
|
|
354
369
|
cluster.tcp = [];
|
|
355
370
|
}
|
|
356
371
|
|
|
372
|
+
const includeTlsReachability =
|
|
373
|
+
// @ts-ignore
|
|
374
|
+
this.webex.config.meetings.experimental.enableTlsReachability && !cluster.isVideoMesh;
|
|
375
|
+
|
|
376
|
+
if (!includeTlsReachability) {
|
|
377
|
+
cluster.xtls = [];
|
|
378
|
+
}
|
|
379
|
+
|
|
357
380
|
this.clusterReachability[key] = new ClusterReachability(key, cluster);
|
|
358
381
|
|
|
359
382
|
return this.clusterReachability[key].start().then((result) => {
|
|
@@ -34,17 +34,21 @@ class ReachabilityRequest {
|
|
|
34
34
|
* @returns {Promise}
|
|
35
35
|
*/
|
|
36
36
|
getClusters = (ipVersion?: IP_VERSION): Promise<{clusters: ClusterList; joinCookie: any}> =>
|
|
37
|
-
this.webex
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
this.webex.internal.newMetrics.callDiagnosticLatencies
|
|
38
|
+
.measureLatency(
|
|
39
|
+
() =>
|
|
40
|
+
this.webex.request({
|
|
41
|
+
method: HTTP_VERBS.GET,
|
|
42
|
+
shouldRefreshAccessToken: false,
|
|
43
|
+
api: API.CALLIOPEDISCOVERY,
|
|
44
|
+
resource: RESOURCE.CLUSTERS,
|
|
45
|
+
qs: {
|
|
46
|
+
JCSupport: 1,
|
|
47
|
+
ipver: ipVersion,
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
'internal.get.cluster.time'
|
|
51
|
+
)
|
|
48
52
|
.then((res) => {
|
|
49
53
|
const {clusters, joinCookie} = res.body;
|
|
50
54
|
|
package/src/reachability/util.ts
CHANGED
|
@@ -22,3 +22,24 @@ export function convertStunUrlToTurn(stunUrl: string, protocol: 'udp' | 'tcp') {
|
|
|
22
22
|
|
|
23
23
|
return url.toString();
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Converts a stun url to a turns url
|
|
28
|
+
*
|
|
29
|
+
* @param {string} stunUrl url of a stun server
|
|
30
|
+
* @returns {string} url of a turns server
|
|
31
|
+
*/
|
|
32
|
+
export function convertStunUrlToTurnTls(stunUrl: string) {
|
|
33
|
+
// stunUrl looks like this: "stun:external-media1.public.wjfkm-a-15.prod.infra.webex.com:443"
|
|
34
|
+
// and we need it to be like this: "turns:external-media1.public.wjfkm-a-15.prod.infra.webex.com:443?transport=tcp"
|
|
35
|
+
const url = new URL(stunUrl);
|
|
36
|
+
|
|
37
|
+
if (url.protocol !== 'stun:') {
|
|
38
|
+
throw new Error(`Not a STUN URL: ${stunUrl}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
url.protocol = 'turns:';
|
|
42
|
+
url.searchParams.append('transport', 'tcp');
|
|
43
|
+
|
|
44
|
+
return url.toString();
|
|
45
|
+
}
|
|
@@ -568,7 +568,7 @@ export default class ReconnectionManager {
|
|
|
568
568
|
|
|
569
569
|
const iceServers = [];
|
|
570
570
|
|
|
571
|
-
if (turnServerResult.turnServerInfo) {
|
|
571
|
+
if (turnServerResult.turnServerInfo?.url) {
|
|
572
572
|
iceServers.push({
|
|
573
573
|
urls: turnServerResult.turnServerInfo.url,
|
|
574
574
|
username: turnServerResult.turnServerInfo.username || '',
|
package/src/roap/index.ts
CHANGED
|
@@ -5,12 +5,18 @@ import {ROAP} from '../constants';
|
|
|
5
5
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
6
6
|
|
|
7
7
|
import RoapRequest from './request';
|
|
8
|
-
import TurnDiscovery from './turnDiscovery';
|
|
8
|
+
import TurnDiscovery, {TurnDiscoveryResult} from './turnDiscovery';
|
|
9
9
|
import Meeting from '../meeting';
|
|
10
10
|
import MeetingUtil from '../meeting/util';
|
|
11
11
|
import Metrics from '../metrics';
|
|
12
12
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
13
13
|
|
|
14
|
+
export {
|
|
15
|
+
type TurnDiscoveryResult,
|
|
16
|
+
type TurnServerInfo,
|
|
17
|
+
type TurnDiscoverySkipReason,
|
|
18
|
+
} from './turnDiscovery';
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Roap options
|
|
16
22
|
* @typedef {Object} RoapOptions
|
|
@@ -39,7 +45,7 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
39
45
|
options: any;
|
|
40
46
|
roapHandler: any;
|
|
41
47
|
roapRequest: any;
|
|
42
|
-
turnDiscovery:
|
|
48
|
+
turnDiscovery: TurnDiscovery;
|
|
43
49
|
|
|
44
50
|
/**
|
|
45
51
|
*
|
|
@@ -260,7 +266,23 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
260
266
|
* @param {Boolean} [isForced]
|
|
261
267
|
* @returns {Promise}
|
|
262
268
|
*/
|
|
263
|
-
doTurnDiscovery(
|
|
269
|
+
doTurnDiscovery(
|
|
270
|
+
meeting: Meeting,
|
|
271
|
+
isReconnecting: boolean,
|
|
272
|
+
isForced?: boolean
|
|
273
|
+
): Promise<TurnDiscoveryResult> {
|
|
264
274
|
return this.turnDiscovery.doTurnDiscovery(meeting, isReconnecting, isForced);
|
|
265
275
|
}
|
|
276
|
+
|
|
277
|
+
generateTurnDiscoveryRequestMessage(meeting: Meeting, isForced: boolean) {
|
|
278
|
+
return this.turnDiscovery.generateTurnDiscoveryRequestMessage(meeting, isForced);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
handleTurnDiscoveryHttpResponse(meeting: Meeting, httpResponse: object) {
|
|
282
|
+
return this.turnDiscovery.handleTurnDiscoveryHttpResponse(meeting, httpResponse);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
abortTurnDiscovery() {
|
|
286
|
+
return this.turnDiscovery.abort();
|
|
287
|
+
}
|
|
266
288
|
}
|
package/src/roap/request.ts
CHANGED
|
@@ -123,7 +123,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
|
|
|
123
123
|
);
|
|
124
124
|
const {locus} = res.body;
|
|
125
125
|
|
|
126
|
-
locus.roapSeq = roapMessage.seq;
|
|
126
|
+
locus.roapSeq = options.roapMessage.seq;
|
|
127
127
|
|
|
128
128
|
return {
|
|
129
129
|
locus,
|
|
@@ -139,9 +139,9 @@ export default class RoapRequest extends StatelessWebexPlugin {
|
|
|
139
139
|
rawError: err,
|
|
140
140
|
},
|
|
141
141
|
});
|
|
142
|
-
LoggerProxy.logger.error(`Roap:request#sendRoap --> Error
|
|
142
|
+
LoggerProxy.logger.error(`Roap:request#sendRoap --> Error:`, err);
|
|
143
143
|
LoggerProxy.logger.error(
|
|
144
|
-
`Roap:request#sendRoapRequest -->
|
|
144
|
+
`Roap:request#sendRoapRequest --> roapMessage that caused error:${JSON.stringify(
|
|
145
145
|
roapMessage,
|
|
146
146
|
null,
|
|
147
147
|
2
|