@webex/plugin-meetings 3.12.0-next.3 → 3.12.0-next.31
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +23 -21
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +550 -346
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +222 -61
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/index.js +372 -292
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +146 -62
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +39 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +5 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +116 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +53 -15
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +38 -5
- package/dist/types/meeting/index.d.ts +11 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/multistream/sendSlotManager.d.ts +23 -1
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +16 -16
- package/src/constants.ts +1 -0
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +273 -154
- package/src/hashTree/utils.ts +17 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/locus-info/index.ts +233 -79
- package/src/meeting/index.ts +98 -11
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +58 -34
- package/src/meetings/util.ts +44 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +5 -0
- package/src/multistream/sendSlotManager.ts +97 -3
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +839 -37
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/locus-info/index.js +262 -64
- package/test/unit/spec/meeting/index.js +54 -36
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +190 -8
- package/test/unit/spec/meetings/utils.js +124 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
- package/test/unit/spec/webinar/index.ts +60 -0
package/src/meeting/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
MediaConnectionEventNames,
|
|
23
23
|
MediaContent,
|
|
24
24
|
MediaType,
|
|
25
|
+
MediaCodecMimeType,
|
|
25
26
|
RemoteTrackType,
|
|
26
27
|
RoapMessage,
|
|
27
28
|
StatsAnalyzer,
|
|
@@ -3733,7 +3734,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3733
3734
|
});
|
|
3734
3735
|
this.updateLLMConnection();
|
|
3735
3736
|
});
|
|
3736
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST,
|
|
3737
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3737
3738
|
this.stopKeepAlive();
|
|
3738
3739
|
|
|
3739
3740
|
if (payload) {
|
|
@@ -3759,6 +3760,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3759
3760
|
});
|
|
3760
3761
|
}
|
|
3761
3762
|
this.rtcMetrics?.sendNextMetrics();
|
|
3763
|
+
|
|
3764
|
+
this.ensureDefaultDatachannelTokenAfterAdmit().catch((error) => {
|
|
3765
|
+
LoggerProxy.logger.warn(
|
|
3766
|
+
`Meeting:index#setUpLocusInfoSelfListener --> failed post-admit token prefetch flow: ${
|
|
3767
|
+
error?.message || String(error)
|
|
3768
|
+
}`
|
|
3769
|
+
);
|
|
3770
|
+
});
|
|
3771
|
+
|
|
3762
3772
|
this.updateLLMConnection();
|
|
3763
3773
|
});
|
|
3764
3774
|
|
|
@@ -5959,6 +5969,30 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5959
5969
|
);
|
|
5960
5970
|
}
|
|
5961
5971
|
|
|
5972
|
+
/**
|
|
5973
|
+
* Restores LLM subchannel subscriptions after reconnect when captions are active.
|
|
5974
|
+
* @returns {void}
|
|
5975
|
+
*/
|
|
5976
|
+
private restoreLLMSubscriptionsIfNeeded(): void {
|
|
5977
|
+
try {
|
|
5978
|
+
// @ts-ignore
|
|
5979
|
+
const isCaptionBoxOn = this.webex.internal.voicea?.getIsCaptionBoxOn?.();
|
|
5980
|
+
|
|
5981
|
+
if (!isCaptionBoxOn) {
|
|
5982
|
+
return;
|
|
5983
|
+
}
|
|
5984
|
+
|
|
5985
|
+
// @ts-ignore
|
|
5986
|
+
this.webex.internal.voicea.updateSubchannelSubscriptions({subscribe: ['transcription']});
|
|
5987
|
+
} catch (error) {
|
|
5988
|
+
const msg = error?.message || String(error);
|
|
5989
|
+
|
|
5990
|
+
LoggerProxy.logger.warn(
|
|
5991
|
+
`Meeting:index#restoreLLMSubscriptionsIfNeeded --> failed to restore subscriptions after LLM online: ${msg}`
|
|
5992
|
+
);
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
|
|
5962
5996
|
/**
|
|
5963
5997
|
* This is a callback for the LLM event that is triggered when it comes online
|
|
5964
5998
|
* This method in turn will trigger an event to the developers that the LLM is connected
|
|
@@ -5967,8 +6001,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5967
6001
|
* @returns {null}
|
|
5968
6002
|
*/
|
|
5969
6003
|
private handleLLMOnline = (): void => {
|
|
5970
|
-
|
|
5971
|
-
|
|
6004
|
+
this.restoreLLMSubscriptionsIfNeeded();
|
|
6005
|
+
|
|
5972
6006
|
Trigger.trigger(
|
|
5973
6007
|
this,
|
|
5974
6008
|
{
|
|
@@ -6199,6 +6233,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6199
6233
|
this.saveDataChannelToken(join);
|
|
6200
6234
|
// @ts-ignore - config coming from registerPlugin
|
|
6201
6235
|
if (this.config.enableAutomaticLLM) {
|
|
6236
|
+
// @ts-ignore
|
|
6237
|
+
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6202
6238
|
// @ts-ignore
|
|
6203
6239
|
this.webex.internal.llm.on('online', this.handleLLMOnline);
|
|
6204
6240
|
this.updateLLMConnection()
|
|
@@ -6342,6 +6378,52 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6342
6378
|
}
|
|
6343
6379
|
}
|
|
6344
6380
|
|
|
6381
|
+
/**
|
|
6382
|
+
* Ensures default-session data channel token exists after lobby admission.
|
|
6383
|
+
* Some lobby users do not receive a token until they are admitted.
|
|
6384
|
+
* @returns {Promise<boolean>} true when a new token is fetched and cached
|
|
6385
|
+
*/
|
|
6386
|
+
private async ensureDefaultDatachannelTokenAfterAdmit(): Promise<boolean> {
|
|
6387
|
+
try {
|
|
6388
|
+
// @ts-ignore
|
|
6389
|
+
const datachannelToken = this.webex.internal.llm.getDatachannelToken();
|
|
6390
|
+
// @ts-ignore
|
|
6391
|
+
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
6392
|
+
|
|
6393
|
+
if (!isDataChannelTokenEnabled || datachannelToken) {
|
|
6394
|
+
return false;
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6397
|
+
const response = await this.meetingRequest.fetchDatachannelToken({
|
|
6398
|
+
locusUrl: this.locusUrl,
|
|
6399
|
+
requestingParticipantId: this.members.selfId,
|
|
6400
|
+
isPracticeSession: false,
|
|
6401
|
+
});
|
|
6402
|
+
const fetchedDatachannelToken = response?.body?.datachannelToken;
|
|
6403
|
+
|
|
6404
|
+
if (!fetchedDatachannelToken) {
|
|
6405
|
+
return false;
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6408
|
+
// @ts-ignore
|
|
6409
|
+
this.webex.internal.llm.setDatachannelToken(
|
|
6410
|
+
fetchedDatachannelToken,
|
|
6411
|
+
DataChannelTokenType.Default
|
|
6412
|
+
);
|
|
6413
|
+
|
|
6414
|
+
return true;
|
|
6415
|
+
} catch (error) {
|
|
6416
|
+
const msg = error?.message || String(error);
|
|
6417
|
+
|
|
6418
|
+
LoggerProxy.logger.warn(
|
|
6419
|
+
`Meeting:index#ensureDefaultDatachannelTokenAfterAdmit --> failed to proactively fetch default data channel token after admit: ${msg}`,
|
|
6420
|
+
{statusCode: error?.statusCode}
|
|
6421
|
+
);
|
|
6422
|
+
|
|
6423
|
+
return false;
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
|
|
6345
6427
|
/**
|
|
6346
6428
|
* Connects to low latency mercury and reconnects if the address has changed
|
|
6347
6429
|
* It will also disconnect if called when the meeting has ended
|
|
@@ -9839,15 +9921,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9839
9921
|
}
|
|
9840
9922
|
|
|
9841
9923
|
if (shouldEnableMusicMode) {
|
|
9842
|
-
await this.sendSlotManager.
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9924
|
+
await this.sendSlotManager.setCustomCodecParameters(
|
|
9925
|
+
MediaType.AudioMain,
|
|
9926
|
+
MediaCodecMimeType.OPUS,
|
|
9927
|
+
{
|
|
9928
|
+
maxaveragebitrate: '64000',
|
|
9929
|
+
maxplaybackrate: '48000',
|
|
9930
|
+
}
|
|
9931
|
+
);
|
|
9846
9932
|
} else {
|
|
9847
|
-
await this.sendSlotManager.
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9933
|
+
await this.sendSlotManager.markCustomCodecParametersForDeletion(
|
|
9934
|
+
MediaType.AudioMain,
|
|
9935
|
+
MediaCodecMimeType.OPUS,
|
|
9936
|
+
['maxaveragebitrate', 'maxplaybackrate']
|
|
9937
|
+
);
|
|
9851
9938
|
}
|
|
9852
9939
|
}
|
|
9853
9940
|
|
package/src/meeting/util.ts
CHANGED
|
@@ -371,6 +371,7 @@ const MeetingUtil = {
|
|
|
371
371
|
meeting.breakouts.cleanUp();
|
|
372
372
|
meeting.webinar.cleanUp();
|
|
373
373
|
meeting.simultaneousInterpretation.cleanUp();
|
|
374
|
+
meeting.locusInfo.cleanUp();
|
|
374
375
|
meeting.locusMediaRequest = undefined;
|
|
375
376
|
|
|
376
377
|
meeting.webex?.internal?.newMetrics?.callDiagnosticMetrics?.clearEventLimitsForCorrelationId(
|
package/src/meetings/index.ts
CHANGED
|
@@ -69,7 +69,9 @@ import JoinForbiddenError from '../common/errors/join-forbidden-error';
|
|
|
69
69
|
import {HashTreeMessage} from '../hashTree/hashTreeParser';
|
|
70
70
|
import {HashTreeObject} from '../hashTree/types';
|
|
71
71
|
import {isSelf} from '../hashTree/utils';
|
|
72
|
+
|
|
72
73
|
import {createLocusFromHashTreeMessage, findMeetingForHashTreeMessage} from '../locus-info';
|
|
74
|
+
import {LocusDTO} from '../locus-info/types';
|
|
73
75
|
|
|
74
76
|
let mediaLogger;
|
|
75
77
|
|
|
@@ -313,7 +315,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
313
315
|
const breakoutLocus = this.meetingCollection.getActiveBreakoutLocus(breakoutUrl);
|
|
314
316
|
|
|
315
317
|
const isSelfJoined = newLocus?.self?.state === _JOINED_;
|
|
316
|
-
const isSelfMoved = newLocus
|
|
318
|
+
const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
|
|
317
319
|
// @ts-ignore
|
|
318
320
|
const deviceFromNewLocus = MeetingsUtil.getThisDevice(newLocus, this.webex.internal.device.url);
|
|
319
321
|
const isResourceMovedOnThisDevice =
|
|
@@ -390,7 +392,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
390
392
|
private isNeedHandleLocusDTO(meeting: any, newLocus: any) {
|
|
391
393
|
if (newLocus) {
|
|
392
394
|
const isNewLocusAsBreakout = MeetingsUtil.isBreakoutLocusDTO(newLocus);
|
|
393
|
-
const isSelfMoved = newLocus
|
|
395
|
+
const isSelfMoved = MeetingsUtil.isSelfMovedOrBreakoutEnded(newLocus);
|
|
394
396
|
const isSelfMovedToLobby =
|
|
395
397
|
newLocus?.self?.devices[0]?.intent?.reason === _ON_HOLD_LOBBY_ &&
|
|
396
398
|
newLocus?.self?.devices[0]?.intent?.type === _WAIT_;
|
|
@@ -435,14 +437,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
435
437
|
if (existingMeeting) {
|
|
436
438
|
return existingMeeting;
|
|
437
439
|
}
|
|
438
|
-
|
|
439
440
|
if (data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED) {
|
|
440
441
|
// need to check if maybe this event indicates a move to/from breakout
|
|
441
442
|
const meetingForHashTreeMessage = findMeetingForHashTreeMessage(
|
|
442
|
-
data
|
|
443
|
-
this.meetingCollection
|
|
444
|
-
// @ts-ignore
|
|
445
|
-
this.webex.internal.device.url
|
|
443
|
+
data?.stateElementsMessage,
|
|
444
|
+
this.meetingCollection
|
|
446
445
|
);
|
|
447
446
|
|
|
448
447
|
if (meetingForHashTreeMessage) {
|
|
@@ -492,7 +491,6 @@ export default class Meetings extends WebexPlugin {
|
|
|
492
491
|
*/
|
|
493
492
|
private handleLocusEvent(data: LocusEvent, useRandomDelayForInfo = false) {
|
|
494
493
|
let meeting = this.getCorrespondingMeetingByLocus(data);
|
|
495
|
-
|
|
496
494
|
// @ts-ignore
|
|
497
495
|
if (this.config.experimental.storeLocusHashTreeEventsForDebugging) {
|
|
498
496
|
storeEventForDebugging('mercury', data);
|
|
@@ -604,7 +602,6 @@ export default class Meetings extends WebexPlugin {
|
|
|
604
602
|
// @ts-ignore
|
|
605
603
|
this.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
|
|
606
604
|
}
|
|
607
|
-
|
|
608
605
|
this.checkHandleBreakoutLocus(data.locus);
|
|
609
606
|
})
|
|
610
607
|
.catch((e) => {
|
|
@@ -1811,7 +1808,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
1811
1808
|
// For type LOCUS_ID we need to parse the locus object to get the information
|
|
1812
1809
|
// about the caller and callee
|
|
1813
1810
|
// Meeting Added event will be created in `handleLocusEvent`
|
|
1814
|
-
if
|
|
1811
|
+
// Only emit MEETING_ADDED if the meeting still exists in the collection.
|
|
1812
|
+
// If fetchMeetingInfo failed and the meeting was destroyed in the catch block,
|
|
1813
|
+
// skip emitting to prevent orphaned meeting references on the consumer side.
|
|
1814
|
+
// @ts-ignore - getMeetingByType types value as object but accepts strings (same as handleLocusEvent)
|
|
1815
|
+
if (type !== DESTINATION_TYPE.LOCUS_ID && this.getMeetingByType(_ID_, meeting.id)) {
|
|
1815
1816
|
if (!meeting.sipUri) {
|
|
1816
1817
|
meeting.setSipUri(destination);
|
|
1817
1818
|
}
|
|
@@ -1886,23 +1887,20 @@ export default class Meetings extends WebexPlugin {
|
|
|
1886
1887
|
* @public
|
|
1887
1888
|
* @memberof Meetings
|
|
1888
1889
|
*/
|
|
1889
|
-
public syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1890
|
+
public async syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
|
|
1890
1891
|
// @ts-ignore
|
|
1891
1892
|
if (this.webex.credentials.isUnverifiedGuest) {
|
|
1892
1893
|
LoggerProxy.logger.info(
|
|
1893
|
-
'Meetings:index#syncMeetings --> skipping
|
|
1894
|
+
'Meetings:index#syncMeetings --> user is unverified guest, skipping calling Locus for meeting sync'
|
|
1894
1895
|
);
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
return this.request
|
|
1900
|
-
.getActiveMeetings()
|
|
1901
|
-
.then((locusArray) => {
|
|
1902
|
-
const activeLocusUrl = [];
|
|
1896
|
+
} else {
|
|
1897
|
+
try {
|
|
1898
|
+
const locusArray = await this.request.getActiveMeetings();
|
|
1899
|
+
const activeLocusUrl: string[] = [];
|
|
1903
1900
|
|
|
1904
1901
|
if (locusArray?.loci && locusArray.loci.length > 0) {
|
|
1905
1902
|
const lociToUpdate = this.sortLocusArrayToUpdate(locusArray.loci);
|
|
1903
|
+
|
|
1906
1904
|
lociToUpdate.forEach((locus) => {
|
|
1907
1905
|
activeLocusUrl.push(locus.url);
|
|
1908
1906
|
this.handleLocusEvent({
|
|
@@ -1920,21 +1918,48 @@ export default class Meetings extends WebexPlugin {
|
|
|
1920
1918
|
// (they had a locusUrl previously but are no longer active) in the sync
|
|
1921
1919
|
for (const meeting of Object.values(meetingsCollection)) {
|
|
1922
1920
|
// @ts-ignore
|
|
1923
|
-
const {locusUrl} = meeting;
|
|
1921
|
+
const {locusUrl, locusInfo} = meeting;
|
|
1924
1922
|
if ((keepOnlyLocusMeetings || locusUrl) && !activeLocusUrl.includes(locusUrl)) {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1923
|
+
const globalMeetingId = locusInfo?.info?.globalMeetingId;
|
|
1924
|
+
|
|
1925
|
+
if (
|
|
1926
|
+
globalMeetingId &&
|
|
1927
|
+
locusArray?.loci?.some(
|
|
1928
|
+
(locus: LocusDTO) => locus.info?.globalMeetingId === globalMeetingId
|
|
1929
|
+
)
|
|
1930
|
+
) {
|
|
1931
|
+
// don't destroy the meeting as Locus API still returned some Locus that shares
|
|
1932
|
+
// the same globalMeetingId - that happens for example if a webinar user (who hasn't scheduled it)
|
|
1933
|
+
// is in a breakout and gets moved to a different breakout while we were offline
|
|
1934
|
+
} else {
|
|
1935
|
+
// destroy function also uploads logs
|
|
1936
|
+
// @ts-ignore
|
|
1937
|
+
this.destroy(meeting, MEETING_REMOVED_REASON.NO_MEETINGS_TO_SYNC);
|
|
1938
|
+
}
|
|
1928
1939
|
}
|
|
1929
1940
|
}
|
|
1930
1941
|
}
|
|
1931
|
-
})
|
|
1932
|
-
.catch((error) => {
|
|
1942
|
+
} catch (error) {
|
|
1933
1943
|
LoggerProxy.logger.error(
|
|
1934
1944
|
`Meetings:index#syncMeetings --> failed to sync meetings, ${error}`
|
|
1935
1945
|
);
|
|
1936
|
-
throw
|
|
1937
|
-
}
|
|
1946
|
+
throw error;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// Trigger hash tree syncs for all remaining meetings
|
|
1951
|
+
const remainingMeetings = this.meetingCollection.getAll();
|
|
1952
|
+
const syncPromises = [];
|
|
1953
|
+
|
|
1954
|
+
for (const meeting of Object.values(remainingMeetings) as any[]) {
|
|
1955
|
+
if (meeting.locusInfo) {
|
|
1956
|
+
syncPromises.push(meeting.locusInfo.syncAllHashTreeDatasets());
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (syncPromises.length > 0) {
|
|
1961
|
+
await Promise.all(syncPromises);
|
|
1962
|
+
}
|
|
1938
1963
|
}
|
|
1939
1964
|
|
|
1940
1965
|
/**
|
|
@@ -1950,8 +1975,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1950
1975
|
this.breakoutLocusForHandleLater = [];
|
|
1951
1976
|
const lociToUpdate = [...mainLoci];
|
|
1952
1977
|
breakoutLoci.forEach((breakoutLocus) => {
|
|
1953
|
-
const associateMainLocus = mainLoci.find(
|
|
1954
|
-
(mainLocus
|
|
1978
|
+
const associateMainLocus = mainLoci.find((mainLocus) =>
|
|
1979
|
+
MeetingsUtil.isMainAssociatedWithBreakout(mainLocus, breakoutLocus)
|
|
1955
1980
|
);
|
|
1956
1981
|
const existCorrespondingMeeting = this.getCorrespondingMeetingByLocus({
|
|
1957
1982
|
eventType: LOCUSEVENT.SDK_NO_EVENT,
|
|
@@ -1979,7 +2004,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1979
2004
|
* @public
|
|
1980
2005
|
* @memberof Meetings
|
|
1981
2006
|
*/
|
|
1982
|
-
checkHandleBreakoutLocus(newCreatedLocus) {
|
|
2007
|
+
checkHandleBreakoutLocus(newCreatedLocus: any) {
|
|
1983
2008
|
if (
|
|
1984
2009
|
!newCreatedLocus ||
|
|
1985
2010
|
!this.breakoutLocusForHandleLater ||
|
|
@@ -1990,9 +2015,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1990
2015
|
if (MeetingsUtil.isBreakoutLocusDTO(newCreatedLocus)) {
|
|
1991
2016
|
return;
|
|
1992
2017
|
}
|
|
1993
|
-
const existIndex = this.breakoutLocusForHandleLater.findIndex(
|
|
1994
|
-
(breakoutLocus)
|
|
1995
|
-
breakoutLocus.controls?.breakout?.url === newCreatedLocus.controls?.breakout?.url
|
|
2018
|
+
const existIndex = this.breakoutLocusForHandleLater.findIndex((breakoutLocus: any) =>
|
|
2019
|
+
MeetingsUtil.isMainAssociatedWithBreakout(newCreatedLocus, breakoutLocus)
|
|
1996
2020
|
);
|
|
1997
2021
|
|
|
1998
2022
|
if (existIndex < 0) {
|
package/src/meetings/util.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
_LEFT_,
|
|
8
8
|
DESTINATION_TYPE,
|
|
9
9
|
_MOVED_,
|
|
10
|
+
_BREAKOUT_ENDED_,
|
|
10
11
|
BREAKOUTS,
|
|
11
12
|
EVENT_TRIGGERS,
|
|
12
13
|
LOCUS,
|
|
@@ -266,6 +267,23 @@ MeetingsUtil.getThisDevice = (newLocus: any, deviceUrl: string) => {
|
|
|
266
267
|
return null;
|
|
267
268
|
};
|
|
268
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Checks if the self state in a locus indicates a breakout move or breakout end.
|
|
272
|
+
* Returns true when:
|
|
273
|
+
* - self state is LEFT with reason MOVED (regular breakout move), OR
|
|
274
|
+
* - fullState is INACTIVE with endMeetingReason BREAKOUT_ENDED (breakout session ended)
|
|
275
|
+
* @param {Object} locus locus data
|
|
276
|
+
* @returns {boolean}
|
|
277
|
+
*/
|
|
278
|
+
MeetingsUtil.isSelfMovedOrBreakoutEnded = (locus: any): boolean => {
|
|
279
|
+
const isSelfLeftMoved = locus?.self?.state === _LEFT_ && locus?.self?.reason === _MOVED_;
|
|
280
|
+
const isBreakoutEnded =
|
|
281
|
+
locus?.fullState?.state === LOCUS.STATE.INACTIVE &&
|
|
282
|
+
locus?.fullState?.endMeetingReason === _BREAKOUT_ENDED_;
|
|
283
|
+
|
|
284
|
+
return isSelfLeftMoved || isBreakoutEnded;
|
|
285
|
+
};
|
|
286
|
+
|
|
269
287
|
/**
|
|
270
288
|
* get self device joined status from locus data
|
|
271
289
|
* @param {Object} meeting current meeting data
|
|
@@ -294,7 +312,10 @@ MeetingsUtil.joinedOnThisDevice = (meeting: any, newLocus: any, deviceUrl: strin
|
|
|
294
312
|
* @private
|
|
295
313
|
*/
|
|
296
314
|
MeetingsUtil.isBreakoutLocusDTO = (newLocus: any) => {
|
|
297
|
-
return
|
|
315
|
+
return (
|
|
316
|
+
newLocus?.controls?.breakout?.sessionType === BREAKOUTS.SESSION_TYPES.BREAKOUT ||
|
|
317
|
+
!!newLocus?.info?.isBreakout
|
|
318
|
+
);
|
|
298
319
|
};
|
|
299
320
|
|
|
300
321
|
/**
|
|
@@ -310,4 +331,26 @@ MeetingsUtil.isValidBreakoutLocus = (locus: any) => {
|
|
|
310
331
|
|
|
311
332
|
return isLocusAsBreakout && !inActiveStatus && selfJoined;
|
|
312
333
|
};
|
|
334
|
+
/**
|
|
335
|
+
* check if the breakout locus is associated with the main locus by comparing the breakout control url or the replaces info in self device
|
|
336
|
+
* @param {Object} mainLocus main locus data
|
|
337
|
+
* @param {Object} breakoutLocus breakout locus data
|
|
338
|
+
* @returns {boolean}
|
|
339
|
+
* @private
|
|
340
|
+
*/
|
|
341
|
+
MeetingsUtil.isMainAssociatedWithBreakout = (mainLocus: any, breakoutLocus: any) => {
|
|
342
|
+
if (
|
|
343
|
+
mainLocus.controls?.breakout?.url &&
|
|
344
|
+
mainLocus.controls?.breakout?.url === breakoutLocus.controls?.breakout?.url
|
|
345
|
+
) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
const deviceUrl = breakoutLocus?.self?.deviceUrl;
|
|
349
|
+
const replaceInfo = MeetingsUtil.getThisDevice(breakoutLocus, deviceUrl)?.replaces?.[0];
|
|
350
|
+
if (replaceInfo?.locusUrl && replaceInfo.locusUrl === mainLocus.url) {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return false;
|
|
355
|
+
};
|
|
313
356
|
export default MeetingsUtil;
|
package/src/member/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export default class Member {
|
|
|
27
27
|
isModerator: any;
|
|
28
28
|
isModeratorAssignmentProhibited: any;
|
|
29
29
|
isPresenterAssignmentProhibited: any;
|
|
30
|
+
isAttendeeAssignmentProhibited: any;
|
|
30
31
|
isMutable: any;
|
|
31
32
|
isNotAdmitted: any;
|
|
32
33
|
isRecording: any;
|
|
@@ -292,6 +293,14 @@ export default class Member {
|
|
|
292
293
|
*/
|
|
293
294
|
this.isPresenterAssignmentProhibited = null;
|
|
294
295
|
|
|
296
|
+
/**
|
|
297
|
+
* @instance
|
|
298
|
+
* @type {Boolean}
|
|
299
|
+
* @public
|
|
300
|
+
* @memberof Member
|
|
301
|
+
*/
|
|
302
|
+
this.isAttendeeAssignmentProhibited = null;
|
|
303
|
+
|
|
295
304
|
/**
|
|
296
305
|
* @instance
|
|
297
306
|
* @type {Boolean}
|
|
@@ -369,6 +378,7 @@ export default class Member {
|
|
|
369
378
|
MemberUtil.isModeratorAssignmentProhibited(participant);
|
|
370
379
|
this.isPresenterAssignmentProhibited =
|
|
371
380
|
MemberUtil.isPresenterAssignmentProhibited(participant);
|
|
381
|
+
this.isAttendeeAssignmentProhibited = MemberUtil.isAttendeeAssignmentProhibited(participant);
|
|
372
382
|
this.canApproveAIEnablement = MemberUtil.canApproveAIEnablement(participant);
|
|
373
383
|
this.processStatus(participant);
|
|
374
384
|
this.processRoles(participant);
|
package/src/member/types.ts
CHANGED
|
@@ -103,6 +103,7 @@ export interface Participant {
|
|
|
103
103
|
moderator: boolean; // Locus docs say this is deprecated and role control should be used instead
|
|
104
104
|
moderatorAssignmentNotAllowed: boolean;
|
|
105
105
|
presenterAssignmentNotAllowed: boolean;
|
|
106
|
+
attendeeAssignmentNotAllowed?: boolean;
|
|
106
107
|
person: ParticipantPerson;
|
|
107
108
|
resourceGuest: boolean;
|
|
108
109
|
state: string; // probably one of MEETING_STATE.STATES
|
package/src/member/util.ts
CHANGED
|
@@ -140,6 +140,9 @@ const MemberUtil = {
|
|
|
140
140
|
isPresenterAssignmentProhibited: (participant: Participant) =>
|
|
141
141
|
participant && participant.presenterAssignmentNotAllowed,
|
|
142
142
|
|
|
143
|
+
isAttendeeAssignmentProhibited: (participant: Participant) =>
|
|
144
|
+
!!(participant && participant.attendeeAssignmentNotAllowed),
|
|
145
|
+
|
|
143
146
|
/**
|
|
144
147
|
* checks to see if the participant id is the same as the passed id
|
|
145
148
|
* there are multiple ids that can be used
|
package/src/metrics/constants.ts
CHANGED
|
@@ -91,6 +91,11 @@ const BEHAVIORAL_METRICS = {
|
|
|
91
91
|
LOCUS_CLASSIC_VS_HASH_TREE_MISMATCH: 'js_sdk_locus_classic_vs_hash_tree_mismatch',
|
|
92
92
|
LOCUS_HASH_TREE_UNSUPPORTED_OPERATION: 'js_sdk_locus_hash_tree_unsupported_operation',
|
|
93
93
|
MEDIA_STILL_NOT_CONNECTED: 'js_sdk_media_still_not_connected',
|
|
94
|
+
DEPRECATED_SET_CODEC_PARAMETERS_USED: 'js_sdk_deprecated_set_codec_parameters_used',
|
|
95
|
+
DEPRECATED_DELETE_CODEC_PARAMETERS_USED: 'js_sdk_deprecated_delete_codec_parameters_used',
|
|
96
|
+
SET_CUSTOM_CODEC_PARAMETERS_USED: 'js_sdk_set_custom_codec_parameters_used',
|
|
97
|
+
MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED:
|
|
98
|
+
'js_sdk_mark_custom_codec_parameters_for_deletion_used',
|
|
94
99
|
};
|
|
95
100
|
|
|
96
101
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
MultistreamRoapMediaConnection,
|
|
6
6
|
NamedMediaGroup,
|
|
7
7
|
StreamState,
|
|
8
|
+
MediaCodecMimeType,
|
|
9
|
+
CodecParameters,
|
|
8
10
|
} from '@webex/internal-media-core';
|
|
11
|
+
import Metrics from '../metrics';
|
|
12
|
+
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* This class is used to manage the sendSlots for the given media types.
|
|
@@ -206,6 +210,8 @@ export default class SendSlotManager {
|
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
/**
|
|
213
|
+
* @deprecated Use {@link setCustomCodecParameters} instead, which requires specifying the codec MIME type.
|
|
214
|
+
*
|
|
209
215
|
* This method is used to set the codec parameters for the sendSlot of the given mediaType
|
|
210
216
|
* @param {MediaType} mediaType MediaType of the sendSlot for which the codec parameters needs to be set (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
|
|
211
217
|
* @param {Object} codecParameters
|
|
@@ -226,12 +232,19 @@ export default class SendSlotManager {
|
|
|
226
232
|
|
|
227
233
|
await slot.setCodecParameters(codecParameters);
|
|
228
234
|
|
|
229
|
-
this.LoggerProxy.logger.
|
|
230
|
-
|
|
235
|
+
this.LoggerProxy.logger.warn(
|
|
236
|
+
'SendSlotsManager->setCodecParameters --> [DEPRECATION WARNING]: setCodecParameters has been deprecated, use setCustomCodecParameters instead'
|
|
231
237
|
);
|
|
238
|
+
|
|
239
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.DEPRECATED_SET_CODEC_PARAMETERS_USED, {
|
|
240
|
+
mediaType,
|
|
241
|
+
codecParameters,
|
|
242
|
+
});
|
|
232
243
|
}
|
|
233
244
|
|
|
234
245
|
/**
|
|
246
|
+
* @deprecated Use {@link markCustomCodecParametersForDeletion} instead, which requires specifying the codec MIME type.
|
|
247
|
+
*
|
|
235
248
|
* This method is used to delete the codec parameters for the sendSlot of the given mediaType
|
|
236
249
|
* @param {MediaType} mediaType MediaType of the sendSlot for which the codec parameters needs to be deleted (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
|
|
237
250
|
* @param {Array<String>} parameters Array of keys of the codec parameters to be deleted
|
|
@@ -246,8 +259,89 @@ export default class SendSlotManager {
|
|
|
246
259
|
|
|
247
260
|
await slot.deleteCodecParameters(parameters);
|
|
248
261
|
|
|
262
|
+
this.LoggerProxy.logger.warn(
|
|
263
|
+
'SendSlotsManager->deleteCodecParameters --> [DEPRECATION WARNING]: deleteCodecParameters has been deprecated, use markCustomCodecParametersForDeletion instead'
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.DEPRECATED_DELETE_CODEC_PARAMETERS_USED, {
|
|
267
|
+
mediaType,
|
|
268
|
+
parameters,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Sets custom codec parameters for the sendSlot of the given mediaType, scoped to a specific codec MIME type.
|
|
274
|
+
* Delegates to WCME's setCustomCodecParameters API.
|
|
275
|
+
* @param {MediaType} mediaType MediaType of the sendSlot
|
|
276
|
+
* @param {MediaCodecMimeType} codecMimeType The codec MIME type to apply parameters to (e.g. OPUS, H264, AV1)
|
|
277
|
+
* @param {CodecParameters} parameters Key-value pairs of codec parameters to set
|
|
278
|
+
* @returns {Promise<void>}
|
|
279
|
+
*/
|
|
280
|
+
public async setCustomCodecParameters(
|
|
281
|
+
mediaType: MediaType,
|
|
282
|
+
codecMimeType: MediaCodecMimeType,
|
|
283
|
+
parameters: CodecParameters
|
|
284
|
+
): Promise<void> {
|
|
285
|
+
const slot = this.slots.get(mediaType);
|
|
286
|
+
|
|
287
|
+
if (!slot) {
|
|
288
|
+
throw new Error(`Slot for ${mediaType} does not exist`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
await slot.setCustomCodecParameters(codecMimeType, parameters);
|
|
293
|
+
|
|
294
|
+
this.LoggerProxy.logger.info(
|
|
295
|
+
`SendSlotsManager->setCustomCodecParameters#Set custom codec parameters for ${mediaType} (codec: ${codecMimeType}) to ${JSON.stringify(
|
|
296
|
+
parameters
|
|
297
|
+
)}`
|
|
298
|
+
);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
this.LoggerProxy.logger.error(
|
|
301
|
+
`SendSlotsManager->setCustomCodecParameters#Failed to set custom codec parameters for ${mediaType} (codec: ${codecMimeType}): ${error}`
|
|
302
|
+
);
|
|
303
|
+
throw error;
|
|
304
|
+
} finally {
|
|
305
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.SET_CUSTOM_CODEC_PARAMETERS_USED, {
|
|
306
|
+
mediaType,
|
|
307
|
+
codecMimeType,
|
|
308
|
+
parameters,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Marks custom codec parameters for deletion on the sendSlot of the given mediaType, scoped to a specific codec MIME type.
|
|
315
|
+
* Delegates to WCME's markCustomCodecParametersForDeletion API.
|
|
316
|
+
* @param {MediaType} mediaType MediaType of the sendSlot
|
|
317
|
+
* @param {MediaCodecMimeType} codecMimeType The codec MIME type whose parameters should be deleted (e.g. OPUS, H264, AV1)
|
|
318
|
+
* @param {string[]} parameters Array of parameter keys to delete
|
|
319
|
+
* @returns {Promise<void>}
|
|
320
|
+
*/
|
|
321
|
+
public async markCustomCodecParametersForDeletion(
|
|
322
|
+
mediaType: MediaType,
|
|
323
|
+
codecMimeType: MediaCodecMimeType,
|
|
324
|
+
parameters: string[]
|
|
325
|
+
): Promise<void> {
|
|
326
|
+
const slot = this.slots.get(mediaType);
|
|
327
|
+
|
|
328
|
+
if (!slot) {
|
|
329
|
+
throw new Error(`Slot for ${mediaType} does not exist`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
await slot.markCustomCodecParametersForDeletion(codecMimeType, parameters);
|
|
333
|
+
|
|
249
334
|
this.LoggerProxy.logger.info(
|
|
250
|
-
`SendSlotsManager->
|
|
335
|
+
`SendSlotsManager->markCustomCodecParametersForDeletion#Marked codec parameters for deletion -> ${parameters} for ${mediaType} (codec: ${codecMimeType})`
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
Metrics.sendBehavioralMetric(
|
|
339
|
+
BEHAVIORAL_METRICS.MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED,
|
|
340
|
+
{
|
|
341
|
+
mediaType,
|
|
342
|
+
codecMimeType,
|
|
343
|
+
parameters,
|
|
344
|
+
}
|
|
251
345
|
);
|
|
252
346
|
}
|
|
253
347
|
|