@webex/plugin-meetings 3.9.0-next.2 → 3.9.0-next.21
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/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +38 -10
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/parser.js +4 -1
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/media/properties.js +53 -5
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +2 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +189 -122
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +2 -5
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +25 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +30 -11
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +29 -21
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +31 -25
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/members/collection.js +13 -0
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +42 -20
- package/dist/members/index.js.map +1 -1
- package/dist/members/util.js +7 -2
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/index.js +3 -3
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/locus-info/index.d.ts +54 -1
- package/dist/types/media/properties.d.ts +21 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
- package/dist/types/meeting/index.d.ts +11 -1
- package/dist/types/meeting/request.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +10 -3
- package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/members/collection.d.ts +6 -0
- package/dist/types/members/index.d.ts +12 -2
- package/dist/types/members/util.d.ts +6 -3
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +17 -17
- package/src/constants.ts +2 -0
- package/src/locus-info/index.ts +84 -9
- package/src/locus-info/parser.ts +5 -1
- package/src/media/properties.ts +43 -0
- package/src/meeting/in-meeting-actions.ts +4 -0
- package/src/meeting/index.ts +91 -4
- package/src/meeting/muteState.ts +2 -6
- package/src/meeting/request.ts +23 -0
- package/src/meeting/util.ts +41 -20
- package/src/meeting-info/meeting-info-v2.ts +24 -5
- package/src/meetings/index.ts +9 -3
- package/src/member/types.ts +1 -0
- package/src/members/collection.ts +11 -0
- package/src/members/index.ts +38 -5
- package/src/members/util.ts +18 -2
- package/src/metrics/constants.ts +1 -0
- package/src/reachability/index.ts +3 -3
- package/test/unit/spec/common/browser-detection.js +0 -24
- package/test/unit/spec/locus-info/index.js +30 -15
- package/test/unit/spec/locus-info/parser.js +3 -2
- package/test/unit/spec/media/properties.ts +137 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +255 -27
- package/test/unit/spec/meeting/muteState.js +32 -6
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +45 -16
- package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
- package/test/unit/spec/meetings/index.js +10 -5
- package/test/unit/spec/members/collection.js +120 -0
- package/test/unit/spec/members/index.js +72 -3
- package/test/unit/spec/members/request.js +55 -0
- package/test/unit/spec/members/utils.js +116 -14
- package/test/unit/spec/reachability/index.ts +158 -3
- package/test/unit/spec/roap/turnDiscovery.ts +3 -3
package/src/locus-info/index.ts
CHANGED
@@ -31,6 +31,51 @@ import LocusDeltaParser from './parser';
|
|
31
31
|
import Metrics from '../metrics';
|
32
32
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
33
33
|
|
34
|
+
export type LocusDTO = {
|
35
|
+
controls?: any;
|
36
|
+
fullState?: {
|
37
|
+
active: boolean;
|
38
|
+
count: number;
|
39
|
+
lastActive: string;
|
40
|
+
locked: boolean;
|
41
|
+
sessionId: string;
|
42
|
+
seessionIds: string[];
|
43
|
+
startTime: number;
|
44
|
+
state: string;
|
45
|
+
type: string;
|
46
|
+
};
|
47
|
+
host?: {
|
48
|
+
id: string;
|
49
|
+
incomingCallProtocols: any[];
|
50
|
+
isExternal: boolean;
|
51
|
+
name: string;
|
52
|
+
orgId: string;
|
53
|
+
};
|
54
|
+
info?: any;
|
55
|
+
links?: any;
|
56
|
+
mediaShares?: any[];
|
57
|
+
meetings?: any[];
|
58
|
+
participants: any[];
|
59
|
+
replaces?: any[];
|
60
|
+
self?: any;
|
61
|
+
sequence?: {
|
62
|
+
dirtyParticipants: number;
|
63
|
+
entries: number[];
|
64
|
+
rangeEnd: number;
|
65
|
+
rangeStart: number;
|
66
|
+
sequenceHash: number;
|
67
|
+
sessionToken: string;
|
68
|
+
since: string;
|
69
|
+
totalParticipants: number;
|
70
|
+
};
|
71
|
+
syncUrl?: string;
|
72
|
+
url?: string;
|
73
|
+
};
|
74
|
+
|
75
|
+
export type LocusApiResponseBody = {
|
76
|
+
locus: LocusDTO; // this LocusDTO here might not be the full one (for example it won't have all the participants, but it should have self)
|
77
|
+
};
|
78
|
+
|
34
79
|
/**
|
35
80
|
* @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
|
36
81
|
* @export
|
@@ -93,19 +138,26 @@ export default class LocusInfo extends EventsScope {
|
|
93
138
|
* Does a Locus sync. It tries to get the latest delta DTO or if it can't, it falls back to getting the full Locus DTO.
|
94
139
|
*
|
95
140
|
* @param {Meeting} meeting
|
141
|
+
* @param {boolean} isLocusUrlChanged
|
142
|
+
* @param {Locus} locus
|
96
143
|
* @returns {undefined}
|
97
144
|
*/
|
98
|
-
private doLocusSync(meeting: any) {
|
99
|
-
let isDelta;
|
145
|
+
private doLocusSync(meeting: any, isLocusUrlChanged: boolean, locus: any) {
|
100
146
|
let url;
|
147
|
+
let isDelta = false;
|
101
148
|
let meetingDestroyed = false;
|
102
149
|
|
103
|
-
if (
|
150
|
+
if (isLocusUrlChanged) {
|
151
|
+
// for the locus url changed case from breakout to main session, we should always do a full sync, in this case, the url from locus is always on main session,
|
152
|
+
// so use the main session locus url to get the full locus(full participants list in the response).
|
153
|
+
// for the locus url changed case from main session to breakout, we don't need to care about it here,
|
154
|
+
// because it is a USE_INCOMING case, it will not be executed here.
|
155
|
+
url = locus.url;
|
156
|
+
} else if (this.locusParser.workingCopy?.syncUrl) {
|
104
157
|
url = this.locusParser.workingCopy.syncUrl;
|
105
158
|
isDelta = true;
|
106
159
|
} else {
|
107
160
|
url = meeting.locusUrl;
|
108
|
-
isDelta = false;
|
109
161
|
}
|
110
162
|
|
111
163
|
LoggerProxy.logger.info(
|
@@ -217,6 +269,7 @@ export default class LocusInfo extends EventsScope {
|
|
217
269
|
*/
|
218
270
|
applyLocusDeltaData(action: string, locus: any, meeting: any) {
|
219
271
|
const {DESYNC, USE_CURRENT, USE_INCOMING, WAIT, LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
|
272
|
+
const isLocusUrlChanged = action === LOCUS_URL_CHANGED;
|
220
273
|
|
221
274
|
switch (action) {
|
222
275
|
case USE_INCOMING:
|
@@ -228,7 +281,7 @@ export default class LocusInfo extends EventsScope {
|
|
228
281
|
break;
|
229
282
|
case DESYNC:
|
230
283
|
case LOCUS_URL_CHANGED:
|
231
|
-
this.doLocusSync(meeting);
|
284
|
+
this.doLocusSync(meeting, isLocusUrlChanged, locus);
|
232
285
|
break;
|
233
286
|
default:
|
234
287
|
LoggerProxy.logger.info(
|
@@ -286,7 +339,7 @@ export default class LocusInfo extends EventsScope {
|
|
286
339
|
this.updateLocusCache(locus);
|
287
340
|
// above section only updates the locusInfo object
|
288
341
|
// The below section makes sure it updates the locusInfo as well as updates the meeting object
|
289
|
-
this.updateParticipants(locus.participants);
|
342
|
+
this.updateParticipants(locus.participants, []);
|
290
343
|
// For 1:1 space meeting the conversation Url does not exist in locus.conversation
|
291
344
|
this.updateConversationUrl(locus.conversationUrl, locus.info);
|
292
345
|
this.updateControls(locus.controls, locus.self);
|
@@ -315,6 +368,16 @@ export default class LocusInfo extends EventsScope {
|
|
315
368
|
this.emitChange = true;
|
316
369
|
}
|
317
370
|
|
371
|
+
/**
|
372
|
+
* Handles HTTP response from Locus API call.
|
373
|
+
* @param {Meeting} meeting meeting object
|
374
|
+
* @param {LocusApiResponseBody} responseBody body of the http response from Locus API call
|
375
|
+
* @returns {void}
|
376
|
+
*/
|
377
|
+
handleLocusAPIResponse(meeting, responseBody: LocusApiResponseBody): void {
|
378
|
+
this.handleLocusDelta(responseBody.locus, meeting);
|
379
|
+
}
|
380
|
+
|
318
381
|
/**
|
319
382
|
* @param {Meeting} meeting
|
320
383
|
* @param {Object} data
|
@@ -327,6 +390,8 @@ export default class LocusInfo extends EventsScope {
|
|
327
390
|
const locus = this.getTheLocusToUpdate(data.locus);
|
328
391
|
LoggerProxy.logger.info(`Locus-info:index#parse --> received locus data: ${eventType}`);
|
329
392
|
|
393
|
+
locus.jsSdkMeta = {removedParticipantIds: []};
|
394
|
+
|
330
395
|
switch (eventType) {
|
331
396
|
case LOCUSEVENT.PARTICIPANT_JOIN:
|
332
397
|
case LOCUSEVENT.PARTICIPANT_LEFT:
|
@@ -392,7 +457,11 @@ export default class LocusInfo extends EventsScope {
|
|
392
457
|
this.participants = locus.participants;
|
393
458
|
const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
|
394
459
|
this.updateLocusInfo(locus);
|
395
|
-
this.updateParticipants(
|
460
|
+
this.updateParticipants(
|
461
|
+
locus.participants,
|
462
|
+
locus.jsSdkMeta?.removedParticipantIds,
|
463
|
+
isReplaceMembers
|
464
|
+
);
|
396
465
|
this.isMeetingActive();
|
397
466
|
this.handleOneOnOneEvent(eventType);
|
398
467
|
this.updateEmbeddedApps(locus.embeddedApps);
|
@@ -454,7 +523,11 @@ export default class LocusInfo extends EventsScope {
|
|
454
523
|
const isReplaceMembers = ControlsUtils.isNeedReplaceMembers(this.controls, locus.controls);
|
455
524
|
this.mergeParticipants(this.participants, locus.participants);
|
456
525
|
this.updateLocusInfo(locus);
|
457
|
-
this.updateParticipants(
|
526
|
+
this.updateParticipants(
|
527
|
+
locus.participants,
|
528
|
+
locus.jsSdkMeta?.removedParticipantIds,
|
529
|
+
isReplaceMembers
|
530
|
+
);
|
458
531
|
this.isMeetingActive();
|
459
532
|
}
|
460
533
|
|
@@ -745,11 +818,12 @@ export default class LocusInfo extends EventsScope {
|
|
745
818
|
/**
|
746
819
|
* update meeting's members
|
747
820
|
* @param {Object} participants new participants object
|
821
|
+
* @param {Array} removedParticipantIds list of removed participants
|
748
822
|
* @param {Boolean} isReplace is replace the whole members
|
749
823
|
* @returns {Array} updatedParticipants
|
750
824
|
* @memberof LocusInfo
|
751
825
|
*/
|
752
|
-
updateParticipants(participants: object, isReplace?: boolean) {
|
826
|
+
updateParticipants(participants: object, removedParticipantIds?: string[], isReplace?: boolean) {
|
753
827
|
this.emitScoped(
|
754
828
|
{
|
755
829
|
file: 'locus-info',
|
@@ -758,6 +832,7 @@ export default class LocusInfo extends EventsScope {
|
|
758
832
|
EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS,
|
759
833
|
{
|
760
834
|
participants,
|
835
|
+
removedParticipantIds,
|
761
836
|
recordingId: this.parsedLocus.controls && this.parsedLocus.controls.record?.modifiedBy,
|
762
837
|
selfIdentity: this.parsedLocus.self && this.parsedLocus.self.selfIdentity,
|
763
838
|
selfId: this.parsedLocus.self && this.parsedLocus.self.selfId,
|
package/src/locus-info/parser.ts
CHANGED
@@ -728,13 +728,17 @@ export default class Parser {
|
|
728
728
|
break;
|
729
729
|
|
730
730
|
case USE_INCOMING:
|
731
|
-
case LOCUS_URL_CHANGED:
|
732
731
|
// update working copy for future comparisons.
|
733
732
|
// Note: The working copy of parser gets updated in .onFullLocus()
|
734
733
|
// and here when USE_INCOMING or LOCUS_URL_CHANGED locus.
|
735
734
|
this.workingCopy = newLoci;
|
736
735
|
break;
|
737
736
|
|
737
|
+
case LOCUS_URL_CHANGED:
|
738
|
+
// clear the working copy completely, do a full locus sync
|
739
|
+
this.workingCopy = null;
|
740
|
+
break;
|
741
|
+
|
738
742
|
case WAIT:
|
739
743
|
// we've taken newLoci from the front of the queue, so put it back there as we have to wait
|
740
744
|
// for the one that should be in front of it, before we can process it
|
package/src/media/properties.ts
CHANGED
@@ -9,9 +9,12 @@ import {
|
|
9
9
|
|
10
10
|
import {parse} from '@webex/ts-sdp';
|
11
11
|
import {ClientEvent} from '@webex/internal-plugin-metrics';
|
12
|
+
import {throttle} from 'lodash';
|
13
|
+
import Metrics from '../metrics';
|
12
14
|
import {MEETINGS, QUALITY_LEVELS} from '../constants';
|
13
15
|
import LoggerProxy from '../common/logs/logger-proxy';
|
14
16
|
import MediaConnectionAwaiter from './MediaConnectionAwaiter';
|
17
|
+
import BEHAVIORAL_METRICS from '../metrics/constants';
|
15
18
|
|
16
19
|
export type MediaDirection = {
|
17
20
|
sendAudio: boolean;
|
@@ -41,6 +44,8 @@ export default class MediaProperties {
|
|
41
44
|
videoDeviceId: any;
|
42
45
|
videoStream?: LocalCameraStream;
|
43
46
|
namespace = MEETINGS;
|
47
|
+
mediaIssueCounters: {[key: string]: number} = {};
|
48
|
+
throttledSendMediaIssueMetric: ReturnType<typeof throttle>;
|
44
49
|
|
45
50
|
/**
|
46
51
|
* @param {Object} [options] -- to auto construct
|
@@ -66,6 +71,15 @@ export default class MediaProperties {
|
|
66
71
|
this.remoteQualityLevel = QUALITY_LEVELS.HIGH;
|
67
72
|
this.mediaSettings = {};
|
68
73
|
this.videoDeviceId = null;
|
74
|
+
|
75
|
+
this.throttledSendMediaIssueMetric = throttle((eventPayload) => {
|
76
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
|
77
|
+
...eventPayload,
|
78
|
+
});
|
79
|
+
Object.keys(this.mediaIssueCounters).forEach((key) => {
|
80
|
+
this.mediaIssueCounters[key] = 0;
|
81
|
+
});
|
82
|
+
}, 1000 * 60 * 5); // at most once every 5 minutes
|
69
83
|
}
|
70
84
|
|
71
85
|
/**
|
@@ -139,8 +153,14 @@ export default class MediaProperties {
|
|
139
153
|
this.videoDeviceId = deviceId;
|
140
154
|
}
|
141
155
|
|
156
|
+
/**
|
157
|
+
* Clears the webrtcMediaConnection. This method should be called after
|
158
|
+
* peer connection is closed and no longer needed.
|
159
|
+
* @returns {void}
|
160
|
+
*/
|
142
161
|
unsetPeerConnection() {
|
143
162
|
this.webrtcMediaConnection = null;
|
163
|
+
this.throttledSendMediaIssueMetric.flush();
|
144
164
|
}
|
145
165
|
|
146
166
|
/**
|
@@ -424,4 +444,27 @@ export default class MediaProperties {
|
|
424
444
|
};
|
425
445
|
}
|
426
446
|
}
|
447
|
+
|
448
|
+
/**
|
449
|
+
* Sends a metric about a media issue. Metrics are throttled so that we don't
|
450
|
+
* send too many of them, but include a count so that we know how many issues
|
451
|
+
* were detected.
|
452
|
+
*
|
453
|
+
* @param {string} issueType
|
454
|
+
* @param {string} issueSubType
|
455
|
+
* @param {string} correlationId
|
456
|
+
* @returns {void}
|
457
|
+
*/
|
458
|
+
public sendMediaIssueMetric(issueType: string, issueSubType: string, correlationId) {
|
459
|
+
const key = `${issueType}_${issueSubType}`;
|
460
|
+
|
461
|
+
const count = (this.mediaIssueCounters[key] || 0) + 1;
|
462
|
+
|
463
|
+
this.mediaIssueCounters[key] = count;
|
464
|
+
|
465
|
+
this.throttledSendMediaIssueMetric({
|
466
|
+
correlationId,
|
467
|
+
...this.mediaIssueCounters,
|
468
|
+
});
|
469
|
+
}
|
427
470
|
}
|
@@ -44,6 +44,7 @@ interface IInMeetingActions {
|
|
44
44
|
|
45
45
|
isManualCaptionActive?: boolean;
|
46
46
|
isSaveTranscriptsEnabled?: boolean;
|
47
|
+
isSpokenLanguageAutoDetectionEnabled?: boolean;
|
47
48
|
isWebexAssistantActive?: boolean;
|
48
49
|
canViewCaptionPanel?: boolean;
|
49
50
|
isRealTimeTranslationEnabled?: boolean;
|
@@ -187,6 +188,8 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
187
188
|
|
188
189
|
isSaveTranscriptsEnabled = null;
|
189
190
|
|
191
|
+
isSpokenLanguageAutoDetectionEnabled = null;
|
192
|
+
|
190
193
|
isWebexAssistantActive = null;
|
191
194
|
|
192
195
|
canViewCaptionPanel = null;
|
@@ -363,6 +366,7 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
363
366
|
canStopManualCaption: this.canStopManualCaption,
|
364
367
|
isManualCaptionActive: this.isManualCaptionActive,
|
365
368
|
isSaveTranscriptsEnabled: this.isSaveTranscriptsEnabled,
|
369
|
+
isSpokenLanguageAutoDetectionEnabled: this.isSpokenLanguageAutoDetectionEnabled,
|
366
370
|
isWebexAssistantActive: this.isWebexAssistantActive,
|
367
371
|
canViewCaptionPanel: this.canViewCaptionPanel,
|
368
372
|
isRealTimeTranslationEnabled: this.isRealTimeTranslationEnabled,
|
package/src/meeting/index.ts
CHANGED
@@ -28,6 +28,8 @@ import {
|
|
28
28
|
StatsAnalyzerEventNames,
|
29
29
|
NetworkQualityEventNames,
|
30
30
|
NetworkQualityMonitor,
|
31
|
+
StatsMonitor,
|
32
|
+
StatsMonitorEventNames,
|
31
33
|
} from '@webex/internal-media-core';
|
32
34
|
|
33
35
|
import {
|
@@ -269,6 +271,7 @@ export enum ScreenShareFloorStatus {
|
|
269
271
|
type FetchMeetingInfoParams = {
|
270
272
|
password?: string;
|
271
273
|
registrationId?: string;
|
274
|
+
classificationId?: string;
|
272
275
|
captchaCode?: string;
|
273
276
|
extraParams?: Record<string, any>;
|
274
277
|
sendCAevents?: boolean;
|
@@ -633,6 +636,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
633
636
|
shareStatus: string;
|
634
637
|
screenShareFloorState: ScreenShareFloorStatus;
|
635
638
|
statsAnalyzer: StatsAnalyzer;
|
639
|
+
statsMonitor: StatsMonitor;
|
636
640
|
transcription: Transcription;
|
637
641
|
updateMediaConnections: (mediaConnections: any[]) => void;
|
638
642
|
userDisplayHints: any;
|
@@ -1286,6 +1290,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
1286
1290
|
* @memberof Meeting
|
1287
1291
|
*/
|
1288
1292
|
this.networkQualityMonitor = null;
|
1293
|
+
/**
|
1294
|
+
* @instance
|
1295
|
+
* @type {StatsMonitor}
|
1296
|
+
* @private
|
1297
|
+
* @memberof Meeting
|
1298
|
+
*/
|
1299
|
+
this.statsMonitor = null;
|
1289
1300
|
/**
|
1290
1301
|
* Indicates network status of the webrtc media connection
|
1291
1302
|
* @instance
|
@@ -1902,6 +1913,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
1902
1913
|
extraParams = {},
|
1903
1914
|
sendCAevents = false,
|
1904
1915
|
registrationId = null,
|
1916
|
+
classificationId = null,
|
1905
1917
|
}): Promise<void> {
|
1906
1918
|
try {
|
1907
1919
|
const captchaInfo = captchaCode
|
@@ -1918,7 +1930,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
1918
1930
|
this.locusId,
|
1919
1931
|
extraParams,
|
1920
1932
|
{meetingId: this.id, sendCAevents},
|
1921
|
-
registrationId
|
1933
|
+
registrationId,
|
1934
|
+
null,
|
1935
|
+
classificationId
|
1922
1936
|
);
|
1923
1937
|
|
1924
1938
|
this.parseMeetingInfo(info?.body, this.destination, info?.errors);
|
@@ -3184,6 +3198,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
3184
3198
|
this.shareCAEventSentStatus.receiveStart = false;
|
3185
3199
|
this.shareCAEventSentStatus.receiveStop = false;
|
3186
3200
|
|
3201
|
+
let finalBeneficiaryId = contentShare.beneficiaryId;
|
3202
|
+
// In case of attendee in webinar, the whiteboard is shared by other participants
|
3203
|
+
if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
|
3204
|
+
if (!finalBeneficiaryId && whiteboardShare.beneficiaryId) {
|
3205
|
+
finalBeneficiaryId = whiteboardShare.beneficiaryId;
|
3206
|
+
}
|
3207
|
+
}
|
3208
|
+
|
3187
3209
|
Trigger.trigger(
|
3188
3210
|
this,
|
3189
3211
|
{
|
@@ -3192,7 +3214,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
3192
3214
|
},
|
3193
3215
|
EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
3194
3216
|
{
|
3195
|
-
memberId:
|
3217
|
+
memberId: finalBeneficiaryId,
|
3196
3218
|
url: contentShare.url,
|
3197
3219
|
shareInstanceId: this.remoteShareInstanceId,
|
3198
3220
|
annotationInfo: contentShare.annotation,
|
@@ -4214,6 +4236,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
4214
4236
|
isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
|
4215
4237
|
isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
|
4216
4238
|
isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
|
4239
|
+
isSpokenLanguageAutoDetectionEnabled: MeetingUtil.isSpokenLanguageAutoDetectionEnabled(
|
4240
|
+
this.userDisplayHints
|
4241
|
+
),
|
4217
4242
|
isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
|
4218
4243
|
canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(this.userDisplayHints),
|
4219
4244
|
isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
|
@@ -6796,6 +6821,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
6796
6821
|
// @ts-ignore
|
6797
6822
|
this.webex.internal.newMetrics.submitClientEvent({
|
6798
6823
|
name: 'client.ice.start',
|
6824
|
+
payload: {
|
6825
|
+
// @ts-ignore
|
6826
|
+
labels: MeetingUtil.getCaEventLabelsForIpVersion(this.webex),
|
6827
|
+
},
|
6799
6828
|
options: {
|
6800
6829
|
meetingId: this.id,
|
6801
6830
|
},
|
@@ -6965,10 +6994,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
6965
6994
|
}
|
6966
6995
|
}
|
6967
6996
|
|
6968
|
-
// Count members that are in the meeting.
|
6997
|
+
// Count members that are in the meeting or in the lobby.
|
6969
6998
|
const {members} = this.getMembers().membersCollection;
|
6970
6999
|
event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
|
6971
|
-
(member: Member) => member.isInMeeting
|
7000
|
+
(member: Member) => member.isInMeeting || member.isInLobby
|
6972
7001
|
).length;
|
6973
7002
|
|
6974
7003
|
// @ts-ignore
|
@@ -7327,10 +7356,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7327
7356
|
if (this.config.stats.enableStatsAnalyzer) {
|
7328
7357
|
// @ts-ignore - config coming from registerPlugin
|
7329
7358
|
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
7359
|
+
this.statsMonitor = new StatsMonitor();
|
7330
7360
|
this.statsAnalyzer = new StatsAnalyzer({
|
7331
7361
|
// @ts-ignore - config coming from registerPlugin
|
7332
7362
|
config: this.config.stats,
|
7333
7363
|
networkQualityMonitor: this.networkQualityMonitor,
|
7364
|
+
statsMonitor: this.statsMonitor,
|
7334
7365
|
isMultistream: this.isMultistream,
|
7335
7366
|
});
|
7336
7367
|
this.shareCAEventSentStatus = {
|
@@ -7344,6 +7375,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7344
7375
|
NetworkQualityEventNames.NETWORK_QUALITY,
|
7345
7376
|
this.sendNetworkQualityEvent.bind(this)
|
7346
7377
|
);
|
7378
|
+
|
7379
|
+
this.statsMonitor.on(StatsMonitorEventNames.INBOUND_AUDIO_ISSUE, (data) => {
|
7380
|
+
// Before forwarding any inbound audio issues to the app, make sure that we have at least one other
|
7381
|
+
// participant in the meeting with unmuted audio.
|
7382
|
+
// We don't check this.mediaProperties.mediaDirection here, because that's already handled in statsAnalyzer,
|
7383
|
+
// so we won't get this event if we are not setup to receive any audio
|
7384
|
+
const atLeastOneUnmutedOtherMember = Object.values(
|
7385
|
+
this.members.membersCollection.getAll()
|
7386
|
+
).find((member) => {
|
7387
|
+
return !member.isSelf && !member.isPairedWithSelf && !member.isAudioMuted;
|
7388
|
+
});
|
7389
|
+
|
7390
|
+
if (atLeastOneUnmutedOtherMember) {
|
7391
|
+
this.mediaProperties.sendMediaIssueMetric(
|
7392
|
+
'inbound_audio',
|
7393
|
+
data.issueSubType,
|
7394
|
+
this.correlationId
|
7395
|
+
);
|
7396
|
+
|
7397
|
+
Trigger.trigger(
|
7398
|
+
this,
|
7399
|
+
{file: 'meeting/index', function: 'createStatsAnalyzer'},
|
7400
|
+
EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
|
7401
|
+
data
|
7402
|
+
);
|
7403
|
+
}
|
7404
|
+
});
|
7347
7405
|
}
|
7348
7406
|
}
|
7349
7407
|
|
@@ -7642,6 +7700,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7642
7700
|
}
|
7643
7701
|
|
7644
7702
|
this.statsAnalyzer = null;
|
7703
|
+
this.networkQualityMonitor?.removeAllListeners();
|
7704
|
+
this.networkQualityMonitor = null;
|
7705
|
+
this.statsMonitor?.removeAllListeners();
|
7706
|
+
this.statsMonitor = null;
|
7645
7707
|
|
7646
7708
|
// when media fails, we want to upload a webrtc dump to see whats going on
|
7647
7709
|
// this function is async, but returns once the stats have been gathered
|
@@ -7665,6 +7727,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7665
7727
|
await this.statsAnalyzer.stopAnalyzer();
|
7666
7728
|
}
|
7667
7729
|
this.statsAnalyzer = null;
|
7730
|
+
this.networkQualityMonitor?.removeAllListeners();
|
7731
|
+
this.networkQualityMonitor = null;
|
7732
|
+
this.statsMonitor?.removeAllListeners();
|
7733
|
+
this.statsMonitor = null;
|
7668
7734
|
|
7669
7735
|
this.isMultistream = false;
|
7670
7736
|
|
@@ -7836,6 +7902,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7836
7902
|
|
7837
7903
|
this.allowMediaInLobby = options?.allowMediaInLobby;
|
7838
7904
|
|
7905
|
+
// @ts-ignore
|
7906
|
+
const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
|
7907
|
+
|
7839
7908
|
// If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
|
7840
7909
|
// @ts-ignore - isUserUnadmitted coming from SelfUtil
|
7841
7910
|
if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
|
@@ -7934,6 +8003,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
7934
8003
|
locus_id: this.locusUrl.split('/').pop(),
|
7935
8004
|
connectionType,
|
7936
8005
|
ipVersion,
|
8006
|
+
ipver,
|
7937
8007
|
selectedCandidatePairChanges,
|
7938
8008
|
numTransports,
|
7939
8009
|
isMultistream: this.isMultistream,
|
@@ -8002,6 +8072,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
8002
8072
|
...reachabilityMetrics,
|
8003
8073
|
...iceCandidateErrors,
|
8004
8074
|
iceCandidatesCount: this.iceCandidatesCount,
|
8075
|
+
ipver,
|
8005
8076
|
});
|
8006
8077
|
|
8007
8078
|
await this.cleanUpOnAddMediaFailure();
|
@@ -9907,4 +9978,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
9907
9978
|
|
9908
9979
|
return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
|
9909
9980
|
}
|
9981
|
+
|
9982
|
+
/**
|
9983
|
+
* Notifies the host with the given meeting UUID and display names.
|
9984
|
+
*
|
9985
|
+
* @param {string} meetingUuid - The UUID of the meeting.
|
9986
|
+
* @param {string[]} displayName - An array of display names to notify the host with.
|
9987
|
+
* @returns {Promise<any>} The result of the notifyHost request.
|
9988
|
+
*/
|
9989
|
+
notifyHost(meetingUuid: string, displayName: string[]) {
|
9990
|
+
return this.meetingRequest.notifyHost(
|
9991
|
+
this.meetingInfo.siteFullUrl,
|
9992
|
+
this.locusId,
|
9993
|
+
meetingUuid,
|
9994
|
+
displayName
|
9995
|
+
);
|
9996
|
+
}
|
9910
9997
|
}
|
package/src/meeting/muteState.ts
CHANGED
@@ -291,18 +291,14 @@ export class MuteState {
|
|
291
291
|
);
|
292
292
|
|
293
293
|
return MeetingUtil.remoteUpdateAudioVideo(meeting, audioMuted, videoMuted)
|
294
|
-
.then((
|
294
|
+
.then((response) => {
|
295
295
|
LoggerProxy.logger.info(
|
296
296
|
`Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: local mute (audio=${audioMuted}, video=${videoMuted}) applied to server`
|
297
297
|
);
|
298
298
|
|
299
299
|
this.state.server.localMute = this.type === AUDIO ? audioMuted : videoMuted;
|
300
300
|
|
301
|
-
|
302
|
-
meeting.locusInfo.handleLocusDelta(locus, meeting);
|
303
|
-
}
|
304
|
-
|
305
|
-
return locus;
|
301
|
+
return MeetingUtil.updateLocusFromApiResponse(meeting, response);
|
306
302
|
})
|
307
303
|
.catch((remoteUpdateError) => {
|
308
304
|
LoggerProxy.logger.warn(
|
package/src/meeting/request.ts
CHANGED
@@ -985,4 +985,27 @@ export default class MeetingRequest extends StatelessWebexPlugin {
|
|
985
985
|
body: {videoLayout},
|
986
986
|
});
|
987
987
|
}
|
988
|
+
|
989
|
+
/**
|
990
|
+
* Sends a request to notify the host of a meeting.
|
991
|
+
* @param {string} siteFullUrl - The site URL.
|
992
|
+
* @param {string} locusId - The locus ID.
|
993
|
+
* @param {string} meetingUuid - The meeting UUID.
|
994
|
+
* @param {Array<string>} displayName - The display names to notify the host about.
|
995
|
+
* @returns {Promise}
|
996
|
+
*/
|
997
|
+
notifyHost(siteFullUrl: string, locusId: string, meetingUuid: string, displayName: string[]) {
|
998
|
+
// @ts-ignore
|
999
|
+
return this.request({
|
1000
|
+
method: HTTP_VERBS.POST,
|
1001
|
+
uri: `https://${siteFullUrl}/wbxappapi/v1/meetings/${meetingUuid}/notifyhost`,
|
1002
|
+
body: {
|
1003
|
+
displayName,
|
1004
|
+
size: displayName?.length,
|
1005
|
+
},
|
1006
|
+
headers: {
|
1007
|
+
locusId,
|
1008
|
+
},
|
1009
|
+
});
|
1010
|
+
}
|
988
1011
|
}
|
package/src/meeting/util.ts
CHANGED
@@ -59,18 +59,16 @@ const MeetingUtil = {
|
|
59
59
|
);
|
60
60
|
}
|
61
61
|
|
62
|
-
return meeting.locusMediaRequest
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
})
|
73
|
-
.then((response) => response?.body?.locus);
|
62
|
+
return meeting.locusMediaRequest.send({
|
63
|
+
type: 'LocalMute',
|
64
|
+
selfUrl: meeting.selfUrl,
|
65
|
+
mediaId: meeting.mediaId,
|
66
|
+
sequence: meeting.locusInfo.sequence,
|
67
|
+
muteOptions: {
|
68
|
+
audioMuted,
|
69
|
+
videoMuted,
|
70
|
+
},
|
71
|
+
});
|
74
72
|
},
|
75
73
|
|
76
74
|
hasOwner: (info) => info && info.owner,
|
@@ -115,6 +113,28 @@ const MeetingUtil = {
|
|
115
113
|
return IP_VERSION.unknown;
|
116
114
|
},
|
117
115
|
|
116
|
+
/**
|
117
|
+
* Returns CA event labels related to Orpheus ipver parameter that can be sent to CA with any CA event
|
118
|
+
* @param {any} webex instance
|
119
|
+
* @returns {Array<string>|undefined} array of CA event labels or undefined if no labels should be sent
|
120
|
+
*/
|
121
|
+
getCaEventLabelsForIpVersion(webex: any): Array<string> | undefined {
|
122
|
+
const ipver = MeetingUtil.getIpVersion(webex);
|
123
|
+
|
124
|
+
switch (ipver) {
|
125
|
+
case IP_VERSION.unknown:
|
126
|
+
return undefined;
|
127
|
+
case IP_VERSION.only_ipv4:
|
128
|
+
return ['hasIpv4_true'];
|
129
|
+
case IP_VERSION.only_ipv6:
|
130
|
+
return ['hasIpv6_true'];
|
131
|
+
case IP_VERSION.ipv4_and_ipv6:
|
132
|
+
return ['hasIpv4_true', 'hasIpv6_true'];
|
133
|
+
default:
|
134
|
+
return undefined;
|
135
|
+
}
|
136
|
+
},
|
137
|
+
|
118
138
|
joinMeeting: async (meeting, options) => {
|
119
139
|
if (!meeting) {
|
120
140
|
return Promise.reject(new ParameterError('You need a meeting object.'));
|
@@ -618,6 +638,9 @@ const MeetingUtil = {
|
|
618
638
|
isManualCaptionActive: (displayHints) =>
|
619
639
|
displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_STATUS_ACTIVE),
|
620
640
|
|
641
|
+
isSpokenLanguageAutoDetectionEnabled: (displayHints) =>
|
642
|
+
displayHints.includes(DISPLAY_HINTS.SPOKEN_LANGUAGE_AUTO_DETECTION_ENABLED),
|
643
|
+
|
621
644
|
isWebexAssistantActive: (displayHints) =>
|
622
645
|
displayHints.includes(DISPLAY_HINTS.WEBEX_ASSISTANT_STATUS_ACTIVE),
|
623
646
|
|
@@ -673,22 +696,20 @@ const MeetingUtil = {
|
|
673
696
|
},
|
674
697
|
|
675
698
|
/**
|
676
|
-
* Updates the locus info for the meeting with the
|
677
|
-
* returned from requests
|
699
|
+
* Updates the locus info for the meeting with the locus
|
700
|
+
* information returned from API requests made to Locus
|
678
701
|
* Returns the original response object
|
679
702
|
* @param {Object} meeting The meeting object
|
680
703
|
* @param {Object} response The response of the http request
|
681
704
|
* @returns {Object}
|
682
705
|
*/
|
683
|
-
|
706
|
+
updateLocusFromApiResponse: (meeting, response) => {
|
684
707
|
if (!meeting) {
|
685
708
|
return response;
|
686
709
|
}
|
687
710
|
|
688
|
-
|
689
|
-
|
690
|
-
if (locus) {
|
691
|
-
meeting.locusInfo.handleLocusDelta(locus, meeting);
|
711
|
+
if (response?.body?.locus) {
|
712
|
+
meeting.locusInfo.handleLocusAPIResponse(meeting, response.body);
|
692
713
|
}
|
693
714
|
|
694
715
|
return response;
|
@@ -735,7 +756,7 @@ const MeetingUtil = {
|
|
735
756
|
|
736
757
|
return meeting
|
737
758
|
.request(options)
|
738
|
-
.then((response) => MeetingUtil.
|
759
|
+
.then((response) => MeetingUtil.updateLocusFromApiResponse(meeting, response));
|
739
760
|
};
|
740
761
|
|
741
762
|
return locusDeltaRequest;
|