@webex/plugin-meetings 2.60.1-next.1 → 2.60.1-next.10

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.
Files changed (58) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.d.ts +18 -4
  5. package/dist/constants.js +23 -9
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/locus-info/index.d.ts +1 -1
  10. package/dist/locus-info/index.js +8 -8
  11. package/dist/locus-info/index.js.map +1 -1
  12. package/dist/meeting/index.d.ts +119 -31
  13. package/dist/meeting/index.js +1021 -805
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +25 -18
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.d.ts +16 -0
  18. package/dist/meeting/util.js +71 -0
  19. package/dist/meeting/util.js.map +1 -1
  20. package/dist/meeting/voicea-meeting.d.ts +20 -0
  21. package/dist/meeting/voicea-meeting.js +201 -0
  22. package/dist/meeting/voicea-meeting.js.map +1 -0
  23. package/dist/meetings/index.d.ts +25 -3
  24. package/dist/meetings/index.js +83 -32
  25. package/dist/meetings/index.js.map +1 -1
  26. package/dist/reachability/index.js +11 -6
  27. package/dist/reachability/index.js.map +1 -1
  28. package/dist/reconnection-manager/index.js +3 -1
  29. package/dist/reconnection-manager/index.js.map +1 -1
  30. package/dist/roap/index.js +50 -54
  31. package/dist/roap/index.js.map +1 -1
  32. package/dist/statsAnalyzer/index.js +1 -1
  33. package/dist/statsAnalyzer/index.js.map +1 -1
  34. package/dist/statsAnalyzer/mqaUtil.js +13 -10
  35. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  36. package/dist/webinar/index.js +1 -1
  37. package/package.json +22 -21
  38. package/src/constants.ts +22 -4
  39. package/src/locus-info/index.ts +13 -12
  40. package/src/meeting/index.ts +546 -276
  41. package/src/meeting/request.ts +7 -0
  42. package/src/meeting/util.ts +97 -0
  43. package/src/meeting/voicea-meeting.ts +161 -0
  44. package/src/meetings/index.ts +59 -18
  45. package/src/reachability/index.ts +7 -4
  46. package/src/reconnection-manager/index.ts +1 -1
  47. package/src/roap/index.ts +49 -51
  48. package/src/statsAnalyzer/index.ts +2 -2
  49. package/src/statsAnalyzer/mqaUtil.ts +15 -14
  50. package/test/unit/spec/locus-info/index.js +53 -5
  51. package/test/unit/spec/meeting/index.js +1792 -1139
  52. package/test/unit/spec/meeting/request.js +22 -12
  53. package/test/unit/spec/meeting/utils.js +93 -0
  54. package/test/unit/spec/meetings/index.js +180 -21
  55. package/test/unit/spec/reachability/index.ts +2 -1
  56. package/test/unit/spec/reconnection-manager/index.js +1 -0
  57. package/test/unit/spec/roap/index.ts +28 -42
  58. package/test/unit/spec/stats-analyzer/index.js +415 -30
@@ -33,6 +33,12 @@ import {
33
33
  RemoteStream,
34
34
  } from '@webex/media-helpers';
35
35
 
36
+ import {
37
+ EVENT_TRIGGERS as VOICEAEVENTS,
38
+ TURN_ON_CAPTION_STATUS,
39
+ } from '@webex/internal-plugin-voicea';
40
+ import {processNewCaptions, processHighlightCreated} from './voicea-meeting';
41
+
36
42
  import {
37
43
  MeetingNotActiveError,
38
44
  UserInLobbyError,
@@ -40,6 +46,7 @@ import {
40
46
  UserNotJoinedError,
41
47
  AddMediaFailed,
42
48
  } from '../common/errors/webex-errors';
49
+
43
50
  import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
44
51
  import NetworkQualityMonitor from '../networkQualityMonitor';
45
52
  import LoggerProxy from '../common/logs/logger-proxy';
@@ -59,7 +66,6 @@ import MeetingsUtil from '../meetings/util';
59
66
  import RecordingUtil from '../recording-controller/util';
60
67
  import ControlsOptionsUtil from '../controls-options-manager/util';
61
68
  import MediaUtil from '../media/util';
62
- import Transcription from '../transcription';
63
69
  import {Reactions, SkinTones} from '../reactions/reactions';
64
70
  import PasswordError from '../common/errors/password-error';
65
71
  import CaptchaError from '../common/errors/captcha-error';
@@ -97,7 +103,6 @@ import {
97
103
  SHARE_STATUS,
98
104
  SHARE_STOPPED_REASON,
99
105
  VIDEO,
100
- HTTP_VERBS,
101
106
  SELF_ROLES,
102
107
  INTERPRETATION,
103
108
  SELF_POLICY,
@@ -105,6 +110,7 @@ import {
105
110
  MEETING_PERMISSION_TOKEN_REFRESH_REASON,
106
111
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
107
112
  RECONNECTION,
113
+ LANGUAGE_ENGLISH,
108
114
  } from '../constants';
109
115
  import BEHAVIORAL_METRICS from '../metrics/constants';
110
116
  import ParameterError from '../common/errors/parameter';
@@ -158,6 +164,37 @@ const logRequest = (request: any, {logText = ''}) => {
158
164
  });
159
165
  };
160
166
 
167
+ export type CaptionData = {
168
+ id: string;
169
+ isFinal: boolean;
170
+ translations: Array<string>;
171
+ text: string;
172
+ currentCaptionLanguage: string;
173
+ timestamp: string;
174
+ speaker: string;
175
+ };
176
+
177
+ export type Transcription = {
178
+ languageOptions: {
179
+ captionLanguages?: string; // list of supported caption languages from backend
180
+ maxLanguages?: number;
181
+ spokenLanguages?: Array<string>; // list of supported spoken languages from backend
182
+ currentCaptionLanguage?: string; // current caption language - default is english
183
+ requestedCaptionLanguage?: string; // requested caption language
184
+ currentSpokenLanguage?: string; // current spoken language - default is english
185
+ };
186
+ status: string;
187
+ isListening: boolean;
188
+ commandText: string;
189
+ captions: Array<CaptionData>;
190
+ highlights: Array<any>;
191
+ showCaptionBox: boolean;
192
+ transcribingRequestStatus: string;
193
+ isCaptioning: boolean;
194
+ speakerProxy: Map<string, any>;
195
+ interimCaptions: Map<string, CaptionData>;
196
+ };
197
+
161
198
  export type LocalStreams = {
162
199
  microphone?: LocalMicrophoneStream;
163
200
  camera?: LocalCameraStream;
@@ -196,6 +233,13 @@ export enum ScreenShareFloorStatus {
196
233
  RELEASED = 'floor_released',
197
234
  }
198
235
 
236
+ type FetchMeetingInfoParams = {
237
+ password?: string;
238
+ captchaCode?: string;
239
+ extraParams?: Record<string, any>;
240
+ sendCAevents?: boolean;
241
+ };
242
+
199
243
  /**
200
244
  * MediaDirection
201
245
  * @typedef {Object} MediaDirection
@@ -549,7 +593,6 @@ export default class Meeting extends StatelessWebexPlugin {
549
593
  screenShareFloorState: ScreenShareFloorStatus;
550
594
  statsAnalyzer: StatsAnalyzer;
551
595
  transcription: Transcription;
552
- receiveTranscription: boolean;
553
596
  updateMediaConnections: (mediaConnections: any[]) => void;
554
597
  userDisplayHints: any;
555
598
  endCallInitJoinReq: any;
@@ -579,8 +622,53 @@ export default class Meeting extends StatelessWebexPlugin {
579
622
  environment: string;
580
623
  namespace = MEETINGS;
581
624
  allowMediaInLobby: boolean;
625
+ localShareInstanceId: string;
626
+ remoteShareInstanceId: string;
582
627
  turnDiscoverySkippedReason: string;
583
628
  turnServerUsed: boolean;
629
+ areVoiceaEventsSetup = false;
630
+ voiceaListenerCallbacks: object = {
631
+ [VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
632
+ this.transcription.languageOptions = payload;
633
+ Trigger.trigger(
634
+ this,
635
+ {
636
+ file: 'meeting/index',
637
+ function: 'setUpVoiceaListeners',
638
+ },
639
+ EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION,
640
+ payload
641
+ );
642
+ },
643
+ [VOICEAEVENTS.CAPTIONS_TURNED_ON]: () => {
644
+ this.transcription.status = TURN_ON_CAPTION_STATUS.ENABLED;
645
+ },
646
+ [VOICEAEVENTS.EVA_COMMAND]: (payload) => {
647
+ const {data} = payload;
648
+
649
+ this.transcription.isListening = !!data.isListening;
650
+ this.transcription.commandText = data.text ?? '';
651
+ },
652
+ [VOICEAEVENTS.NEW_CAPTION]: (data) => {
653
+ processNewCaptions({data, meeting: this});
654
+ Trigger.trigger(
655
+ this,
656
+ {
657
+ file: 'meeting/index',
658
+ function: 'setUpVoiceaListeners',
659
+ },
660
+ EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED,
661
+ {
662
+ captions: this.transcription.captions,
663
+ interimCaptions: this.transcription.interimCaptions,
664
+ }
665
+ );
666
+ },
667
+ [VOICEAEVENTS.HIGHLIGHT_CREATED]: (data) => {
668
+ processHighlightCreated({data, meeting: this});
669
+ },
670
+ };
671
+
584
672
  private retriedWithTurnServer: boolean;
585
673
  private sendSlotManager: SendSlotManager = new SendSlotManager(LoggerProxy);
586
674
  private deferSDPAnswer?: Defer; // used for waiting for a response
@@ -1179,7 +1267,18 @@ export default class Meeting extends StatelessWebexPlugin {
1179
1267
  * @private
1180
1268
  * @memberof Meeting
1181
1269
  */
1182
- this.transcription = undefined;
1270
+ this.transcription = {
1271
+ captions: [],
1272
+ highlights: [],
1273
+ isListening: false,
1274
+ commandText: '',
1275
+ languageOptions: {},
1276
+ showCaptionBox: false,
1277
+ transcribingRequestStatus: 'INACTIVE',
1278
+ isCaptioning: false,
1279
+ interimCaptions: {} as Map<string, CaptionData>,
1280
+ speakerProxy: {} as Map<string, any>,
1281
+ } as Transcription;
1183
1282
 
1184
1283
  /**
1185
1284
  * Password status. If it's PASSWORD_STATUS.REQUIRED then verifyPassword() needs to be called
@@ -1233,6 +1332,24 @@ export default class Meeting extends StatelessWebexPlugin {
1233
1332
  */
1234
1333
  this.keepAliveTimerId = null;
1235
1334
 
1335
+ /**
1336
+ * id for tracking Local Share instances in Call Analyzer
1337
+ * @instance
1338
+ * @type {String}
1339
+ * @private
1340
+ * @memberof Meeting
1341
+ */
1342
+ this.localShareInstanceId = null;
1343
+
1344
+ /**
1345
+ * id for tracking Remote Share instances in Call Analyzer
1346
+ * @instance
1347
+ * @type {String}
1348
+ * @private
1349
+ * @memberof Meeting
1350
+ */
1351
+ this.remoteShareInstanceId = null;
1352
+
1236
1353
  /**
1237
1354
  * The class that helps to control recording functions: start, stop, pause, resume, etc
1238
1355
  * @instance
@@ -1387,6 +1504,97 @@ export default class Meeting extends StatelessWebexPlugin {
1387
1504
  this.callStateForMetrics.correlationId = correlationId;
1388
1505
  }
1389
1506
 
1507
+ /**
1508
+ * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1509
+ * @param {any} info
1510
+ * @param {string} [meetingLookupUrl] Lookup url, defined when the meeting info fetched
1511
+ * @returns {void}
1512
+ */
1513
+ private setMeetingInfo(info, meetingLookupUrl) {
1514
+ this.meetingInfo = info ? {...info, meetingLookupUrl} : null;
1515
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
1516
+
1517
+ this.requiredCaptcha = null;
1518
+ if (
1519
+ this.passwordStatus === PASSWORD_STATUS.REQUIRED ||
1520
+ this.passwordStatus === PASSWORD_STATUS.VERIFIED
1521
+ ) {
1522
+ this.passwordStatus = PASSWORD_STATUS.VERIFIED;
1523
+ } else {
1524
+ this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
1525
+ }
1526
+
1527
+ Trigger.trigger(
1528
+ this,
1529
+ {
1530
+ file: 'meetings',
1531
+ function: 'fetchMeetingInfo',
1532
+ },
1533
+ EVENT_TRIGGERS.MEETING_INFO_AVAILABLE
1534
+ );
1535
+
1536
+ this.updateMeetingActions();
1537
+ }
1538
+
1539
+ /**
1540
+ * Add pre-fetched meeting info
1541
+ *
1542
+ * The passed meeting info should be be complete, e.g.: fetched after password or captcha provided
1543
+ *
1544
+ * @param {Object} meetingInfo - Complete meeting info
1545
+ * @param {FetchMeetingInfoParams} fetchParams - Fetch parameters for validation
1546
+ * @param {String|undefined} meetingLookupUrl - Lookup url, defined when the meeting info fetched
1547
+ * @returns {Promise<void>}
1548
+ */
1549
+ public async injectMeetingInfo(
1550
+ meetingInfo: any,
1551
+ fetchParams: FetchMeetingInfoParams,
1552
+ meetingLookupUrl: string | undefined
1553
+ ): Promise<void> {
1554
+ await this.prepForFetchMeetingInfo(fetchParams, 'injectMeetingInfo');
1555
+
1556
+ this.parseMeetingInfo(meetingInfo, this.destination);
1557
+ this.setMeetingInfo(meetingInfo, meetingLookupUrl);
1558
+ }
1559
+
1560
+ /**
1561
+ * Validate fetch parameters and clear the fetchMeetingInfoTimeout timeout
1562
+ *
1563
+ * @param {FetchMeetingInfoParams} fetchParams - fetch parameters for validation
1564
+ * @param {String} caller - Name of the caller for logging
1565
+ *
1566
+ * @returns {Promise<void>}
1567
+ * @private
1568
+ */
1569
+ private prepForFetchMeetingInfo(
1570
+ {password = null, captchaCode = null, extraParams = {}}: FetchMeetingInfoParams,
1571
+ caller: string
1572
+ ): Promise<void> {
1573
+ // when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
1574
+ if (this.fetchMeetingInfoTimeoutId) {
1575
+ clearTimeout(this.fetchMeetingInfoTimeoutId);
1576
+ this.fetchMeetingInfoTimeoutId = undefined;
1577
+ }
1578
+ if (captchaCode && !this.requiredCaptcha) {
1579
+ return Promise.reject(
1580
+ new Error(`${caller}() called with captchaCode when captcha was not required`)
1581
+ );
1582
+ }
1583
+ if (
1584
+ password &&
1585
+ this.passwordStatus !== PASSWORD_STATUS.REQUIRED &&
1586
+ this.passwordStatus !== PASSWORD_STATUS.UNKNOWN
1587
+ ) {
1588
+ return Promise.reject(
1589
+ new Error(`${caller}() called with password when password was not required`)
1590
+ );
1591
+ }
1592
+
1593
+ this.meetingInfoExtraParams = cloneDeep(extraParams);
1594
+
1595
+ return Promise.resolve();
1596
+ }
1597
+
1390
1598
  /**
1391
1599
  * Internal method for fetching meeting info
1392
1600
  *
@@ -1417,29 +1625,8 @@ export default class Meeting extends StatelessWebexPlugin {
1417
1625
  {meetingId: this.id, sendCAevents}
1418
1626
  );
1419
1627
 
1420
- this.parseMeetingInfo(info, this.destination);
1421
- this.meetingInfo = info ? {...info.body, meetingLookupUrl: info?.url} : null;
1422
- this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
1423
- this.requiredCaptcha = null;
1424
- if (
1425
- this.passwordStatus === PASSWORD_STATUS.REQUIRED ||
1426
- this.passwordStatus === PASSWORD_STATUS.VERIFIED
1427
- ) {
1428
- this.passwordStatus = PASSWORD_STATUS.VERIFIED;
1429
- } else {
1430
- this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
1431
- }
1432
-
1433
- Trigger.trigger(
1434
- this,
1435
- {
1436
- file: 'meetings',
1437
- function: 'fetchMeetingInfo',
1438
- },
1439
- EVENT_TRIGGERS.MEETING_INFO_AVAILABLE
1440
- );
1441
-
1442
- this.updateMeetingActions();
1628
+ this.parseMeetingInfo(info?.body, this.destination, info?.errors);
1629
+ this.setMeetingInfo(info?.body, info?.url);
1443
1630
 
1444
1631
  return Promise.resolve();
1445
1632
  } catch (err) {
@@ -1583,46 +1770,13 @@ export default class Meeting extends StatelessWebexPlugin {
1583
1770
  * @memberof Meeting
1584
1771
  * @returns {Promise}
1585
1772
  */
1586
- public async fetchMeetingInfo({
1587
- password = null,
1588
- captchaCode = null,
1589
- extraParams = {},
1590
- sendCAevents = false,
1591
- }: {
1592
- password?: string;
1593
- captchaCode?: string;
1594
- extraParams?: Record<string, any>;
1595
- sendCAevents?: boolean;
1596
- }) {
1597
- // when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
1598
- if (this.fetchMeetingInfoTimeoutId) {
1599
- clearTimeout(this.fetchMeetingInfoTimeoutId);
1600
- this.fetchMeetingInfoTimeoutId = undefined;
1601
- }
1602
- if (captchaCode && !this.requiredCaptcha) {
1603
- return Promise.reject(
1604
- new Error('fetchMeetingInfo() called with captchaCode when captcha was not required')
1605
- );
1606
- }
1607
- if (
1608
- password &&
1609
- this.passwordStatus !== PASSWORD_STATUS.REQUIRED &&
1610
- this.passwordStatus !== PASSWORD_STATUS.UNKNOWN
1611
- ) {
1612
- return Promise.reject(
1613
- new Error('fetchMeetingInfo() called with password when password was not required')
1614
- );
1615
- }
1616
-
1617
- this.meetingInfoExtraParams = cloneDeep(extraParams);
1773
+ public async fetchMeetingInfo(options: FetchMeetingInfoParams) {
1774
+ await this.prepForFetchMeetingInfo(options, 'fetchMeetingInfo');
1618
1775
 
1619
1776
  return this.fetchMeetingInfoInternal({
1620
1777
  destination: this.destination,
1621
1778
  destinationType: this.destinationType,
1622
- password,
1623
- captchaCode,
1624
- extraParams,
1625
- sendCAevents,
1779
+ ...options,
1626
1780
  });
1627
1781
  }
1628
1782
 
@@ -1840,6 +1994,7 @@ export default class Meeting extends StatelessWebexPlugin {
1840
1994
  * @memberof Meeting
1841
1995
  */
1842
1996
  private setUpInterpretationListener() {
1997
+ // TODO: check if its getting used or not
1843
1998
  this.simultaneousInterpretation.on(INTERPRETATION.EVENTS.SUPPORT_LANGUAGES_UPDATE, () => {
1844
1999
  Trigger.trigger(
1845
2000
  this,
@@ -1850,7 +2005,7 @@ export default class Meeting extends StatelessWebexPlugin {
1850
2005
  EVENT_TRIGGERS.MEETING_INTERPRETATION_SUPPORT_LANGUAGES_UPDATE
1851
2006
  );
1852
2007
  });
1853
-
2008
+ // TODO: check if its getting used or not
1854
2009
  this.simultaneousInterpretation.on(
1855
2010
  INTERPRETATION.EVENTS.HANDOFF_REQUESTS_ARRIVED,
1856
2011
  (payload) => {
@@ -1867,6 +2022,49 @@ export default class Meeting extends StatelessWebexPlugin {
1867
2022
  );
1868
2023
  }
1869
2024
 
2025
+ /**
2026
+ * Set up the listeners for captions
2027
+ * @returns {undefined}
2028
+ * @private
2029
+ * @memberof Meeting
2030
+ */
2031
+ private setUpVoiceaListeners() {
2032
+ // @ts-ignore
2033
+ this.webex.internal.voicea.listenToEvents();
2034
+
2035
+ // @ts-ignore
2036
+ this.webex.internal.voicea.on(
2037
+ VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
2038
+ this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
2039
+ );
2040
+
2041
+ // @ts-ignore
2042
+ this.webex.internal.voicea.on(
2043
+ VOICEAEVENTS.CAPTIONS_TURNED_ON,
2044
+ this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
2045
+ );
2046
+
2047
+ // @ts-ignore
2048
+ this.webex.internal.voicea.on(
2049
+ VOICEAEVENTS.EVA_COMMAND,
2050
+ this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
2051
+ );
2052
+
2053
+ // @ts-ignore
2054
+ this.webex.internal.voicea.on(
2055
+ VOICEAEVENTS.NEW_CAPTION,
2056
+ this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
2057
+ );
2058
+
2059
+ // @ts-ignore
2060
+ this.webex.internal.voicea.on(
2061
+ VOICEAEVENTS.HIGHLIGHT_CREATED,
2062
+ this.voiceaListenerCallbacks[VOICEAEVENTS.HIGHLIGHT_CREATED]
2063
+ );
2064
+
2065
+ this.areVoiceaEventsSetup = true;
2066
+ }
2067
+
1870
2068
  /**
1871
2069
  * Set up the locus info listener for meetings disconnected due to inactivity
1872
2070
  * @returns {undefined}
@@ -2186,19 +2384,22 @@ export default class Meeting extends StatelessWebexPlugin {
2186
2384
  this.locusInfo.on(
2187
2385
  LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIBE_UPDATED,
2188
2386
  ({caption, transcribing}) => {
2189
- // @ts-ignore - config coming from registerPlugin
2190
- if (transcribing && !this.transcription && this.config.receiveTranscription) {
2191
- this.startTranscription();
2192
- } else if (!transcribing && this.transcription) {
2193
- Trigger.trigger(
2194
- this,
2195
- {
2196
- file: 'meeting/index',
2197
- function: 'setupLocusControlsListener',
2198
- },
2199
- EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION,
2200
- {caption, transcribing}
2201
- );
2387
+ // user need to be joined to start the llm and receive transcription
2388
+ if (this.isJoined()) {
2389
+ // @ts-ignore - config coming from registerPlugin
2390
+ if (transcribing && !this.transcription) {
2391
+ this.startTranscription();
2392
+ } else if (!transcribing && this.transcription) {
2393
+ Trigger.trigger(
2394
+ this,
2395
+ {
2396
+ file: 'meeting/index',
2397
+ function: 'setupLocusControlsListener',
2398
+ },
2399
+ EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION,
2400
+ {caption, transcribing}
2401
+ );
2402
+ }
2202
2403
  }
2203
2404
  }
2204
2405
  );
@@ -2230,16 +2431,6 @@ export default class Meeting extends StatelessWebexPlugin {
2230
2431
  }
2231
2432
  );
2232
2433
 
2233
- this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN, ({mainLocusUrl}) => {
2234
- this.meetingRequest.getLocusStatusByUrl(mainLocusUrl).catch((error) => {
2235
- // clear main session cache when attendee join into breakout and forbidden to get locus from main locus url,
2236
- // which means main session is not active for the attendee
2237
- if (error?.statusCode === 403) {
2238
- this.locusInfo.clearMainSessionLocusCache();
2239
- }
2240
- });
2241
- });
2242
-
2243
2434
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
2244
2435
  Trigger.trigger(
2245
2436
  this,
@@ -2474,6 +2665,8 @@ export default class Meeting extends StatelessWebexPlugin {
2474
2665
  switch (newShareStatus) {
2475
2666
  case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
2476
2667
  const sendStartedSharingRemote = () => {
2668
+ this.remoteShareInstanceId = contentShare.shareInstanceId;
2669
+
2477
2670
  Trigger.trigger(
2478
2671
  this,
2479
2672
  {
@@ -2484,7 +2677,7 @@ export default class Meeting extends StatelessWebexPlugin {
2484
2677
  {
2485
2678
  memberId: contentShare.beneficiaryId,
2486
2679
  url: contentShare.url,
2487
- shareInstanceId: contentShare.shareInstanceId,
2680
+ shareInstanceId: this.remoteShareInstanceId,
2488
2681
  annotationInfo: contentShare.annotation,
2489
2682
  }
2490
2683
  );
@@ -2521,6 +2714,7 @@ export default class Meeting extends StatelessWebexPlugin {
2521
2714
  name: 'client.share.floor-granted.local',
2522
2715
  payload: {
2523
2716
  mediaType: 'share',
2717
+ shareInstanceId: this.localShareInstanceId,
2524
2718
  },
2525
2719
  options: {meetingId: this.id},
2526
2720
  });
@@ -2563,6 +2757,8 @@ export default class Meeting extends StatelessWebexPlugin {
2563
2757
  } else if (newShareStatus === SHARE_STATUS.REMOTE_SHARE_ACTIVE) {
2564
2758
  // if we got here, then some remote participant has stolen
2565
2759
  // the presentation from another remote participant
2760
+ this.remoteShareInstanceId = contentShare.shareInstanceId;
2761
+
2566
2762
  Trigger.trigger(
2567
2763
  this,
2568
2764
  {
@@ -2573,7 +2769,7 @@ export default class Meeting extends StatelessWebexPlugin {
2573
2769
  {
2574
2770
  memberId: contentShare.beneficiaryId,
2575
2771
  url: contentShare.url,
2576
- shareInstanceId: contentShare.shareInstanceId,
2772
+ shareInstanceId: this.remoteShareInstanceId,
2577
2773
  annotationInfo: contentShare.annotation,
2578
2774
  }
2579
2775
  );
@@ -2852,15 +3048,6 @@ export default class Meeting extends StatelessWebexPlugin {
2852
3048
  });
2853
3049
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
2854
3050
  this.stopKeepAlive();
2855
- // @ts-ignore
2856
- if (!this.transcription && (this.config.receiveTranscription || this.receiveTranscription)) {
2857
- if (this.isTranscriptionSupported()) {
2858
- await this.startTranscription();
2859
- LoggerProxy.logger.info(
2860
- 'Meeting:index#setUpLocusInfoSelfListener --> enabled to receive transcription for guest user!'
2861
- );
2862
- }
2863
- }
2864
3051
 
2865
3052
  if (payload) {
2866
3053
  Trigger.trigger(
@@ -3200,30 +3387,40 @@ export default class Meeting extends StatelessWebexPlugin {
3200
3387
  /**
3201
3388
  * Sets the meeting info on the class instance
3202
3389
  * @param {Object} meetingInfo
3203
- * @param {Object} meetingInfo.body
3204
- * @param {String} meetingInfo.body.conversationUrl
3205
- * @param {String} meetingInfo.body.locusUrl
3206
- * @param {String} meetingInfo.body.sipUri
3207
- * @param {Object} meetingInfo.body.owner
3390
+ * @param {String} meetingInfo.conversationUrl
3391
+ * @param {String} meetingInfo.locusUrl
3392
+ * @param {String} meetingInfo.sipUri
3393
+ * @param {String} [meetingInfo.sipUrl]
3394
+ * @param {String} [meetingInfo.sipMeetingUri]
3395
+ * @param {String} [meetingInfo.meetingNumber]
3396
+ * @param {String} [meetingInfo.meetingJoinUrl]
3397
+ * @param {String} [meetingInfo.hostId]
3398
+ * @param {String} [meetingInfo.permissionToken]
3399
+ * @param {String} [meetingInfo.channel]
3400
+ * @param {Object} meetingInfo.owner
3208
3401
  * @param {Object | String} destination locus object with meeting data or destination string (sip url, meeting link, etc)
3402
+ * @param {Object | String} errors Meeting info request error
3209
3403
  * @returns {undefined}
3210
3404
  * @private
3211
3405
  * @memberof Meeting
3212
3406
  */
3213
3407
  parseMeetingInfo(
3214
- meetingInfo:
3215
- | {
3216
- body: {
3217
- conversationUrl: string;
3218
- locusUrl: string;
3219
- sipUri: string;
3220
- owner: object;
3221
- };
3222
- }
3223
- | any,
3224
- destination: object | string | null = null
3408
+ meetingInfo: {
3409
+ conversationUrl: string;
3410
+ locusUrl: string;
3411
+ sipUri: string;
3412
+ owner: object;
3413
+ sipUrl?: string;
3414
+ sipMeetingUri?: string;
3415
+ meetingNumber?: string;
3416
+ meetingJoinUrl?: string;
3417
+ hostId?: string;
3418
+ permissionToken?: string;
3419
+ channel?: string;
3420
+ },
3421
+ destination: object | string | null = null,
3422
+ errors: any = undefined
3225
3423
  ) {
3226
- const webexMeetingInfo = meetingInfo?.body;
3227
3424
  // We try to use as much info from Locus meeting object, stored in destination
3228
3425
 
3229
3426
  let locusMeetingObject;
@@ -3233,40 +3430,31 @@ export default class Meeting extends StatelessWebexPlugin {
3233
3430
  }
3234
3431
 
3235
3432
  // MeetingInfo will be undefined for 1:1 calls
3236
- if (
3237
- locusMeetingObject ||
3238
- (webexMeetingInfo && !(meetingInfo?.errors && meetingInfo?.errors.length > 0))
3239
- ) {
3433
+ if (locusMeetingObject || (meetingInfo && !(errors?.length > 0))) {
3240
3434
  this.conversationUrl =
3241
- locusMeetingObject?.conversationUrl ||
3242
- webexMeetingInfo?.conversationUrl ||
3243
- this.conversationUrl;
3244
- this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
3435
+ locusMeetingObject?.conversationUrl || meetingInfo?.conversationUrl || this.conversationUrl;
3436
+ this.locusUrl = locusMeetingObject?.url || meetingInfo?.locusUrl || this.locusUrl;
3245
3437
  // @ts-ignore - config coming from registerPlugin
3246
3438
  this.setSipUri(
3247
3439
  // @ts-ignore
3248
3440
  this.config.experimental.enableUnifiedMeetings
3249
- ? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl
3250
- : locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri
3441
+ ? locusMeetingObject?.info.sipUri || meetingInfo?.sipUrl
3442
+ : locusMeetingObject?.info.sipUri || meetingInfo?.sipMeetingUri || this.sipUri
3251
3443
  );
3252
3444
  // @ts-ignore - config coming from registerPlugin
3253
3445
  if (this.config.experimental.enableUnifiedMeetings) {
3254
- this.meetingNumber =
3255
- locusMeetingObject?.info.webExMeetingId || webexMeetingInfo?.meetingNumber;
3256
- this.meetingJoinUrl = webexMeetingInfo?.meetingJoinUrl;
3446
+ this.meetingNumber = locusMeetingObject?.info.webExMeetingId || meetingInfo?.meetingNumber;
3447
+ this.meetingJoinUrl = meetingInfo?.meetingJoinUrl;
3257
3448
  }
3258
3449
  this.owner =
3259
- locusMeetingObject?.info.owner ||
3260
- webexMeetingInfo?.owner ||
3261
- webexMeetingInfo?.hostId ||
3262
- this.owner;
3263
- this.permissionToken = webexMeetingInfo?.permissionToken;
3264
- this.setPermissionTokenPayload(webexMeetingInfo?.permissionToken);
3450
+ locusMeetingObject?.info.owner || meetingInfo?.owner || meetingInfo?.hostId || this.owner;
3451
+ this.permissionToken = meetingInfo?.permissionToken;
3452
+ this.setPermissionTokenPayload(meetingInfo?.permissionToken);
3265
3453
  this.setSelfUserPolicies();
3266
3454
  // Need to populate environment when sending CA event
3267
- this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
3455
+ this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
3268
3456
  }
3269
- MeetingUtil.parseInterpretationInfo(this, webexMeetingInfo);
3457
+ MeetingUtil.parseInterpretationInfo(this, meetingInfo);
3270
3458
  }
3271
3459
 
3272
3460
  /**
@@ -4419,7 +4607,7 @@ export default class Meeting extends StatelessWebexPlugin {
4419
4607
  }
4420
4608
 
4421
4609
  LoggerProxy.logger.error(
4422
- 'Meeting:index#isTranscriptionSupported --> Webex Assistant is not supported'
4610
+ 'Meeting:index#isTranscriptionSupported --> Webex Assistant is not enabled/supported'
4423
4611
  );
4424
4612
 
4425
4613
  return false;
@@ -4440,109 +4628,136 @@ export default class Meeting extends StatelessWebexPlugin {
4440
4628
  }
4441
4629
 
4442
4630
  /**
4443
- * Monitor the Low-Latency Mercury (LLM) web socket connection on `onError` and `onClose` states
4444
- * @private
4445
- * @returns {void}
4631
+ * sets Caption language for the meeting
4632
+ * @param {string} language
4633
+ * @returns {Promise}
4446
4634
  */
4447
- private monitorTranscriptionSocketConnection() {
4448
- this.transcription.onCloseSocket((event) => {
4449
- LoggerProxy.logger.info(
4450
- `Meeting:index#onCloseSocket -->
4451
- unable to continue receiving transcription;
4452
- low-latency mercury web socket connection is closed now.
4453
- ${event}`
4454
- );
4635
+ public setCaptionLanguage(language: string) {
4636
+ return new Promise((resolve, reject) => {
4637
+ if (!this.isTranscriptionSupported()) {
4638
+ LoggerProxy.logger.error(
4639
+ 'Meeting:index#setCaptionLanguage --> Webex Assistant is not enabled/supported'
4640
+ );
4455
4641
 
4456
- this.triggerStopReceivingTranscriptionEvent();
4457
- });
4642
+ reject(new Error('Webex Assistant is not enabled/supported'));
4643
+ }
4458
4644
 
4459
- this.transcription.onErrorSocket((event) => {
4460
- LoggerProxy.logger.error(
4461
- `Meeting:index#onErrorSocket -->
4462
- unable to continue receiving transcription;
4463
- low-latency mercury web socket connection error had occured.
4464
- ${event}`
4465
- );
4645
+ try {
4646
+ const voiceaListenerCaptionUpdate = (payload) => {
4647
+ // @ts-ignore
4648
+ this.webex.internal.voicea.off(
4649
+ VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE,
4650
+ voiceaListenerCaptionUpdate
4651
+ );
4652
+ const {statusCode} = payload;
4466
4653
 
4467
- this.triggerStopReceivingTranscriptionEvent();
4654
+ if (statusCode === 200) {
4655
+ this.transcription.languageOptions = {
4656
+ ...this.transcription.languageOptions,
4657
+ currentCaptionLanguage: language,
4658
+ };
4659
+ resolve(language);
4660
+ } else {
4661
+ reject(payload);
4662
+ }
4663
+ };
4664
+ // @ts-ignore
4665
+ this.webex.internal.voicea.on(
4666
+ VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE,
4667
+ voiceaListenerCaptionUpdate
4668
+ );
4669
+ // @ts-ignore
4670
+ this.webex.internal.voicea.requestLanguage(language);
4671
+ } catch (error) {
4672
+ LoggerProxy.logger.error(`Meeting:index#setCaptionLanguage --> ${error}`);
4468
4673
 
4469
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.RECEIVE_TRANSCRIPTION_FAILURE, {
4470
- correlation_id: this.correlationId,
4471
- reason: 'unexpected error: transcription LLM web socket connection error had occured.',
4472
- event,
4473
- });
4674
+ reject(error);
4675
+ }
4474
4676
  });
4475
4677
  }
4476
4678
 
4477
4679
  /**
4478
- * Request for a WebSocket Url, open and monitor the WebSocket connection
4479
- * @private
4480
- * @returns {Promise<void>} a promise to open the WebSocket connection
4680
+ * sets Spoken language for the meeting
4681
+ * @param {string} language
4682
+ * @returns {Promise}
4481
4683
  */
4482
- private async startTranscription() {
4483
- LoggerProxy.logger.info(
4484
- `Meeting:index#startTranscription -->
4485
- Attempting to generate a web socket url.`
4486
- );
4684
+ public setSpokenLanguage(language: string) {
4685
+ return new Promise((resolve, reject) => {
4686
+ if (!this.isTranscriptionSupported()) {
4687
+ LoggerProxy.logger.error(
4688
+ 'Meeting:index#setCaptionLanguage --> Webex Assistant is not enabled/supported'
4689
+ );
4487
4690
 
4488
- try {
4489
- const {datachannelUrl} = this.locusInfo.info;
4490
- // @ts-ignore - fix type
4491
- const {
4492
- body: {webSocketUrl},
4493
- // @ts-ignore
4494
- } = await this.request({
4495
- method: HTTP_VERBS.POST,
4496
- uri: datachannelUrl,
4497
- body: {deviceUrl: this.deviceUrl},
4498
- });
4691
+ reject(new Error('Webex Assistant is not enabled/supported'));
4692
+ }
4499
4693
 
4500
- LoggerProxy.logger.info(
4501
- `Meeting:index#startTranscription -->
4502
- Generated web socket url succesfully.`
4503
- );
4694
+ try {
4695
+ const voiceaListenerLanguageUpdate = (payload) => {
4696
+ // @ts-ignore
4697
+ this.webex.internal.voicea.off(
4698
+ VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
4699
+ voiceaListenerLanguageUpdate
4700
+ );
4701
+ const {languageCode} = payload;
4504
4702
 
4505
- this.transcription = new Transcription(
4506
- webSocketUrl,
4507
- // @ts-ignore - fix type
4508
- this.webex.sessionId,
4509
- this.members
4510
- );
4703
+ if (languageCode) {
4704
+ this.transcription.languageOptions = {
4705
+ ...this.transcription.languageOptions,
4706
+ currentSpokenLanguage: languageCode,
4707
+ };
4708
+ resolve(languageCode);
4709
+ } else {
4710
+ reject(payload);
4711
+ }
4712
+ };
4713
+ // @ts-ignore
4714
+ this.webex.internal.voicea.on(
4715
+ VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
4716
+ voiceaListenerLanguageUpdate
4717
+ );
4718
+ // @ts-ignore
4719
+ this.webex.internal.voicea.setSpokenLanguage(language);
4720
+ } catch (error) {
4721
+ LoggerProxy.logger.error(`Meeting:index#setSpokenLanguage --> ${error}`);
4722
+
4723
+ reject(error);
4724
+ }
4725
+ });
4726
+ }
4511
4727
 
4728
+ /**
4729
+ * This method will enable the transcription for the current meeting if the meeting has enabled/supports Webex Assistant
4730
+ * @param {Object} options object with spokenlanguage setting
4731
+ * @public
4732
+ * @returns {Promise<void>} a promise to open the WebSocket connection
4733
+ */
4734
+ public async startTranscription(options?: {spokenLanguage?: string}) {
4735
+ if (this.isJoined()) {
4512
4736
  LoggerProxy.logger.info(
4513
- `Meeting:index#startTranscription -->
4514
- opened LLM web socket connection successfully.`
4737
+ 'Meeting:index#startTranscription --> Attempting to enable transcription!'
4515
4738
  );
4516
4739
 
4517
- if (!this.inMeetingActions.isClosedCaptionActive) {
4518
- LoggerProxy.logger.error(
4519
- `Meeting:index#receiveTranscription --> Transcription cannot be started until a licensed user enables it`
4520
- );
4521
- }
4522
-
4523
- // retrieve and pass the payload
4524
- this.transcription.subscribe((payload) => {
4525
- Trigger.trigger(
4526
- this,
4527
- {
4528
- file: 'meeting/index',
4529
- function: 'join',
4530
- },
4531
- EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION,
4532
- payload
4533
- );
4534
- });
4740
+ try {
4741
+ if (!this.areVoiceaEventsSetup) {
4742
+ this.setUpVoiceaListeners();
4743
+ }
4535
4744
 
4536
- this.monitorTranscriptionSocketConnection();
4537
- // @ts-ignore - fix type
4538
- this.transcription.connect(this.webex.credentials.supertoken.access_token);
4539
- } catch (error) {
4540
- LoggerProxy.logger.error(`Meeting:index#startTranscription --> ${error}`);
4541
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.RECEIVE_TRANSCRIPTION_FAILURE, {
4542
- correlation_id: this.correlationId,
4543
- reason: error.message,
4544
- stack: error.stack,
4545
- });
4745
+ if (this.getCurUserType() === 'host') {
4746
+ // @ts-ignore
4747
+ await this.webex.internal.voicea.toggleTranscribing(true, options?.spokenLanguage);
4748
+ }
4749
+ } catch (error) {
4750
+ LoggerProxy.logger.error(`Meeting:index#startTranscription --> ${error}`);
4751
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.RECEIVE_TRANSCRIPTION_FAILURE, {
4752
+ correlation_id: this.correlationId,
4753
+ reason: error.message,
4754
+ stack: error.stack,
4755
+ });
4756
+ }
4757
+ } else {
4758
+ LoggerProxy.logger.error(
4759
+ `Meeting:index#startTranscription --> meeting joined : ${this.isJoined()}`
4760
+ );
4546
4761
  }
4547
4762
  }
4548
4763
 
@@ -4585,13 +4800,43 @@ export default class Meeting extends StatelessWebexPlugin {
4585
4800
  };
4586
4801
 
4587
4802
  /**
4588
- * stop recieving Transcription by closing
4589
- * the web socket connection properly
4803
+ * This method stops receiving transcription for the current meeting
4590
4804
  * @returns {void}
4591
4805
  */
4592
- stopReceivingTranscription() {
4806
+ stopTranscription() {
4593
4807
  if (this.transcription) {
4594
- this.transcription.closeSocket();
4808
+ // @ts-ignore
4809
+ this.webex.internal.voicea.off(
4810
+ VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
4811
+ this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
4812
+ );
4813
+
4814
+ // @ts-ignore
4815
+ this.webex.internal.voicea.off(
4816
+ VOICEAEVENTS.CAPTIONS_TURNED_ON,
4817
+ this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
4818
+ );
4819
+
4820
+ // @ts-ignore
4821
+ this.webex.internal.voicea.off(
4822
+ VOICEAEVENTS.EVA_COMMAND,
4823
+ this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
4824
+ );
4825
+
4826
+ // @ts-ignore
4827
+ this.webex.internal.voicea.off(
4828
+ VOICEAEVENTS.NEW_CAPTION,
4829
+ this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
4830
+ );
4831
+
4832
+ // @ts-ignore
4833
+ this.webex.internal.voicea.off(
4834
+ VOICEAEVENTS.HIGHLIGHT_CREATED,
4835
+ this.voiceaListenerCallbacks[VOICEAEVENTS.HIGHLIGHT_CREATED]
4836
+ );
4837
+
4838
+ this.areVoiceaEventsSetup = false;
4839
+ this.triggerStopReceivingTranscriptionEvent();
4595
4840
  }
4596
4841
  }
4597
4842
 
@@ -4604,7 +4849,7 @@ export default class Meeting extends StatelessWebexPlugin {
4604
4849
  private triggerStopReceivingTranscriptionEvent() {
4605
4850
  LoggerProxy.logger.info(`
4606
4851
  Meeting:index#stopReceivingTranscription -->
4607
- closed transcription LLM web socket connection successfully.`);
4852
+ closed voicea event listeners successfully.`);
4608
4853
 
4609
4854
  Trigger.trigger(
4610
4855
  this,
@@ -4792,7 +5037,6 @@ export default class Meeting extends StatelessWebexPlugin {
4792
5037
  joinSuccess(join);
4793
5038
 
4794
5039
  this.deferJoin = undefined;
4795
- this.receiveTranscription = !!options.receiveTranscription;
4796
5040
 
4797
5041
  return join;
4798
5042
  })
@@ -4836,48 +5080,33 @@ export default class Meeting extends StatelessWebexPlugin {
4836
5080
  .then((join) => {
4837
5081
  // @ts-ignore - config coming from registerPlugin
4838
5082
  if (this.config.enableAutomaticLLM) {
4839
- this.updateLLMConnection().catch((error) => {
4840
- LoggerProxy.logger.error('Meeting:index#join --> Update LLM Connection Failed', error);
4841
-
4842
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LLM_CONNECTION_AFTER_JOIN_FAILURE, {
4843
- correlation_id: this.correlationId,
4844
- reason: error?.message,
4845
- stack: error.stack,
4846
- });
4847
- });
4848
- }
5083
+ this.updateLLMConnection()
5084
+ .catch((error) => {
5085
+ LoggerProxy.logger.error(
5086
+ 'Meeting:index#join --> Transcription Socket Connection Failed',
5087
+ error
5088
+ );
4849
5089
 
4850
- return join;
4851
- })
4852
- .then((join) => {
4853
- if (isBrowser) {
4854
- // @ts-ignore - config coming from registerPlugin
4855
- if (this.config.receiveTranscription || options.receiveTranscription) {
4856
- if (this.isTranscriptionSupported()) {
5090
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LLM_CONNECTION_AFTER_JOIN_FAILURE, {
5091
+ correlation_id: this.correlationId,
5092
+ reason: error?.message,
5093
+ stack: error.stack,
5094
+ });
5095
+ })
5096
+ .then(() => {
4857
5097
  LoggerProxy.logger.info(
4858
- 'Meeting:index#join --> Attempting to enabled to receive transcription!'
5098
+ 'Meeting:index#join --> Transcription Socket Connection Success'
4859
5099
  );
4860
- this.startTranscription().catch((error) => {
4861
- LoggerProxy.logger.error(
4862
- 'Meeting:index#join --> Receive Transcription Failed',
4863
- error
4864
- );
4865
-
4866
- Metrics.sendBehavioralMetric(
4867
- BEHAVIORAL_METRICS.RECEIVE_TRANSCRIPTION_AFTER_JOIN_FAILURE,
4868
- {
4869
- correlation_id: this.correlationId,
4870
- reason: error?.message,
4871
- stack: error.stack,
4872
- }
4873
- );
4874
- });
4875
- }
4876
- }
4877
- } else {
4878
- LoggerProxy.logger.error(
4879
- 'Meeting:index#join --> Receving transcription is not supported on this platform'
4880
- );
5100
+ Trigger.trigger(
5101
+ this,
5102
+ {
5103
+ file: 'meeting/index',
5104
+ function: 'join',
5105
+ },
5106
+ EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED,
5107
+ undefined
5108
+ );
5109
+ });
4881
5110
  }
4882
5111
 
4883
5112
  return join;
@@ -5365,7 +5594,6 @@ export default class Meeting extends StatelessWebexPlugin {
5365
5594
  seq: event.roapMessage.seq,
5366
5595
  tieBreaker: event.roapMessage.tieBreaker,
5367
5596
  meeting: this, // or can pass meeting ID
5368
- reconnect: this.reconnectionManager.isReconnectInProgress(),
5369
5597
  })
5370
5598
  .then(({roapAnswer}) => {
5371
5599
  if (roapAnswer) {
@@ -5682,7 +5910,10 @@ export default class Meeting extends StatelessWebexPlugin {
5682
5910
  // @ts-ignore
5683
5911
  this.webex.internal.newMetrics.submitClientEvent({
5684
5912
  name: 'client.media.tx.start',
5685
- payload: {mediaType: data.type},
5913
+ payload: {
5914
+ mediaType: data.type,
5915
+ shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
5916
+ },
5686
5917
  options: {
5687
5918
  meetingId: this.id,
5688
5919
  },
@@ -5692,7 +5923,10 @@ export default class Meeting extends StatelessWebexPlugin {
5692
5923
  // @ts-ignore
5693
5924
  this.webex.internal.newMetrics.submitClientEvent({
5694
5925
  name: 'client.media.tx.stop',
5695
- payload: {mediaType: data.type},
5926
+ payload: {
5927
+ mediaType: data.type,
5928
+ shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
5929
+ },
5696
5930
  options: {
5697
5931
  meetingId: this.id,
5698
5932
  },
@@ -5711,7 +5945,10 @@ export default class Meeting extends StatelessWebexPlugin {
5711
5945
  // @ts-ignore
5712
5946
  this.webex.internal.newMetrics.submitClientEvent({
5713
5947
  name: 'client.media.rx.start',
5714
- payload: {mediaType: data.type},
5948
+ payload: {
5949
+ mediaType: data.type,
5950
+ shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
5951
+ },
5715
5952
  options: {
5716
5953
  meetingId: this.id,
5717
5954
  },
@@ -5721,7 +5958,10 @@ export default class Meeting extends StatelessWebexPlugin {
5721
5958
  // @ts-ignore
5722
5959
  this.webex.internal.newMetrics.submitClientEvent({
5723
5960
  name: 'client.media.rx.stop',
5724
- payload: {mediaType: data.type},
5961
+ payload: {
5962
+ mediaType: data.type,
5963
+ shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
5964
+ },
5725
5965
  options: {
5726
5966
  meetingId: this.id,
5727
5967
  },
@@ -6967,11 +7207,14 @@ export default class Meeting extends StatelessWebexPlugin {
6967
7207
  if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
6968
7208
  // @ts-ignore
6969
7209
  this.webex.internal.newMetrics.submitClientEvent({
6970
- name: 'client.share.initiated',
7210
+ name: 'client.share.floor-grant.request',
6971
7211
  payload: {
6972
7212
  mediaType: 'share',
7213
+ shareInstanceId: this.localShareInstanceId,
7214
+ },
7215
+ options: {
7216
+ meetingId: this.id,
6973
7217
  },
6974
- options: {meetingId: this.id},
6975
7218
  });
6976
7219
 
6977
7220
  return this.meetingRequest
@@ -7002,6 +7245,19 @@ export default class Meeting extends StatelessWebexPlugin {
7002
7245
  stack: error.stack,
7003
7246
  });
7004
7247
 
7248
+ // @ts-ignore
7249
+ this.webex.internal.newMetrics.submitClientEvent({
7250
+ name: 'client.share.floor-granted.local',
7251
+ payload: {
7252
+ mediaType: 'share',
7253
+ errors: MeetingUtil.getChangeMeetingFloorErrorPayload(error.message),
7254
+ shareInstanceId: this.localShareInstanceId,
7255
+ },
7256
+ options: {
7257
+ meetingId: this.id,
7258
+ },
7259
+ });
7260
+
7005
7261
  this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
7006
7262
 
7007
7263
  return Promise.reject(error);
@@ -7053,6 +7309,7 @@ export default class Meeting extends StatelessWebexPlugin {
7053
7309
  name: 'client.share.stopped',
7054
7310
  payload: {
7055
7311
  mediaType: 'share',
7312
+ shareInstanceId: this.localShareInstanceId,
7056
7313
  },
7057
7314
  options: {meetingId: this.id},
7058
7315
  });
@@ -7528,6 +7785,9 @@ export default class Meeting extends StatelessWebexPlugin {
7528
7785
  if (roles.includes(SELF_ROLES.COHOST)) {
7529
7786
  return 'cohost';
7530
7787
  }
7788
+ if (roles.includes(SELF_ROLES.PRESENTER)) {
7789
+ return 'presenter';
7790
+ }
7531
7791
  if (roles.includes(SELF_ROLES.ATTENDEE)) {
7532
7792
  return 'attendee';
7533
7793
  }
@@ -7618,8 +7878,7 @@ export default class Meeting extends StatelessWebexPlugin {
7618
7878
  this.queuedMediaUpdates = [];
7619
7879
 
7620
7880
  if (this.transcription) {
7621
- this.transcription.closeSocket();
7622
- this.triggerStopReceivingTranscriptionEvent();
7881
+ this.stopTranscription();
7623
7882
  this.transcription = undefined;
7624
7883
  }
7625
7884
  };
@@ -7933,6 +8192,17 @@ export default class Meeting extends StatelessWebexPlugin {
7933
8192
  }
7934
8193
 
7935
8194
  if (floorRequestNeeded) {
8195
+ this.localShareInstanceId = uuid.v4();
8196
+
8197
+ // @ts-ignore
8198
+ this.webex.internal.newMetrics.submitClientEvent({
8199
+ name: 'client.share.initiated',
8200
+ payload: {
8201
+ mediaType: 'share',
8202
+ shareInstanceId: this.localShareInstanceId,
8203
+ },
8204
+ options: {meetingId: this.id},
8205
+ });
7936
8206
  // we're sending the http request to Locus to request the screen share floor
7937
8207
  // only after the SDP update, because that's how it's always been done for transcoded meetings
7938
8208
  // and also if sharing from the start, we need confluence to have been created