@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.
- package/README.md +12 -0
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.d.ts +18 -4
- package/dist/constants.js +23 -9
- 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.d.ts +1 -1
- package/dist/locus-info/index.js +8 -8
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/index.d.ts +119 -31
- package/dist/meeting/index.js +1021 -805
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +25 -18
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.d.ts +16 -0
- package/dist/meeting/util.js +71 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting/voicea-meeting.d.ts +20 -0
- package/dist/meeting/voicea-meeting.js +201 -0
- package/dist/meeting/voicea-meeting.js.map +1 -0
- package/dist/meetings/index.d.ts +25 -3
- package/dist/meetings/index.js +83 -32
- package/dist/meetings/index.js.map +1 -1
- package/dist/reachability/index.js +11 -6
- package/dist/reachability/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +3 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +50 -54
- package/dist/roap/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +1 -1
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +13 -10
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -21
- package/src/constants.ts +22 -4
- package/src/locus-info/index.ts +13 -12
- package/src/meeting/index.ts +546 -276
- package/src/meeting/request.ts +7 -0
- package/src/meeting/util.ts +97 -0
- package/src/meeting/voicea-meeting.ts +161 -0
- package/src/meetings/index.ts +59 -18
- package/src/reachability/index.ts +7 -4
- package/src/reconnection-manager/index.ts +1 -1
- package/src/roap/index.ts +49 -51
- package/src/statsAnalyzer/index.ts +2 -2
- package/src/statsAnalyzer/mqaUtil.ts +15 -14
- package/test/unit/spec/locus-info/index.js +53 -5
- package/test/unit/spec/meeting/index.js +1792 -1139
- package/test/unit/spec/meeting/request.js +22 -12
- package/test/unit/spec/meeting/utils.js +93 -0
- package/test/unit/spec/meetings/index.js +180 -21
- package/test/unit/spec/reachability/index.ts +2 -1
- package/test/unit/spec/reconnection-manager/index.js +1 -0
- package/test/unit/spec/roap/index.ts +28 -42
- package/test/unit/spec/stats-analyzer/index.js +415 -30
package/src/meeting/index.ts
CHANGED
|
@@ -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 =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2190
|
-
if (
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
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:
|
|
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:
|
|
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 {
|
|
3204
|
-
* @param {String} meetingInfo.
|
|
3205
|
-
* @param {String} meetingInfo.
|
|
3206
|
-
* @param {String} meetingInfo.
|
|
3207
|
-
* @param {
|
|
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
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
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
|
-
|
|
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 ||
|
|
3250
|
-
: locusMeetingObject?.info.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
|
-
|
|
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
|
-
|
|
3261
|
-
|
|
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 ||
|
|
3455
|
+
this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
|
|
3268
3456
|
}
|
|
3269
|
-
MeetingUtil.parseInterpretationInfo(this,
|
|
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
|
-
*
|
|
4444
|
-
* @
|
|
4445
|
-
* @returns {
|
|
4631
|
+
* sets Caption language for the meeting
|
|
4632
|
+
* @param {string} language
|
|
4633
|
+
* @returns {Promise}
|
|
4446
4634
|
*/
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
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
|
-
|
|
4457
|
-
|
|
4642
|
+
reject(new Error('Webex Assistant is not enabled/supported'));
|
|
4643
|
+
}
|
|
4458
4644
|
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4470
|
-
|
|
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
|
-
*
|
|
4479
|
-
* @
|
|
4480
|
-
* @returns {Promise
|
|
4680
|
+
* sets Spoken language for the meeting
|
|
4681
|
+
* @param {string} language
|
|
4682
|
+
* @returns {Promise}
|
|
4481
4683
|
*/
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
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
|
-
|
|
4489
|
-
|
|
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
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
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
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
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
|
-
|
|
4514
|
-
opened LLM web socket connection successfully.`
|
|
4737
|
+
'Meeting:index#startTranscription --> Attempting to enable transcription!'
|
|
4515
4738
|
);
|
|
4516
4739
|
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
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
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
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
|
-
*
|
|
4589
|
-
* the web socket connection properly
|
|
4803
|
+
* This method stops receiving transcription for the current meeting
|
|
4590
4804
|
* @returns {void}
|
|
4591
4805
|
*/
|
|
4592
|
-
|
|
4806
|
+
stopTranscription() {
|
|
4593
4807
|
if (this.transcription) {
|
|
4594
|
-
|
|
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
|
|
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()
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
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
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
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 -->
|
|
5098
|
+
'Meeting:index#join --> Transcription Socket Connection Success'
|
|
4859
5099
|
);
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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.
|
|
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.
|
|
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
|