@webex/plugin-meetings 2.60.0-next.1 → 2.60.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/enums.js +2 -1
- package/dist/controls-options-manager/enums.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/parser.js +5 -5
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/media/index.js +6 -5
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +4 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +276 -150
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +3 -0
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +14 -29
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +17 -0
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +30 -9
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +3 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reconnection-manager/index.js +27 -28
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/rtcMetrics/index.js +25 -0
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +21 -1
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +16 -16
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +21 -22
- package/src/constants.ts +10 -4
- package/src/controls-options-manager/enums.ts +2 -0
- package/src/locus-info/parser.ts +6 -6
- package/src/media/index.ts +5 -5
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +249 -120
- package/src/meeting-info/meeting-info-v2.ts +4 -0
- package/src/meeting-info/utilv2.ts +6 -19
- package/src/meetings/collection.ts +13 -0
- package/src/meetings/index.ts +28 -10
- package/src/metrics/constants.ts +3 -0
- package/src/reconnection-manager/index.ts +63 -68
- package/src/rtcMetrics/index.ts +24 -0
- package/src/statsAnalyzer/index.ts +30 -1
- package/src/statsAnalyzer/mqaUtil.ts +17 -14
- package/test/unit/spec/media/index.ts +20 -4
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +1253 -157
- package/test/unit/spec/meeting/muteState.js +2 -1
- package/test/unit/spec/meeting-info/meetinginfov2.js +28 -0
- package/test/unit/spec/meetings/collection.js +12 -0
- package/test/unit/spec/meetings/index.js +382 -118
- package/test/unit/spec/member/util.js +0 -31
- package/test/unit/spec/reconnection-manager/index.js +42 -12
- package/test/unit/spec/rtcMetrics/index.ts +20 -0
- package/test/unit/spec/stats-analyzer/index.js +12 -2
|
@@ -216,6 +216,10 @@ export default class MeetingInfoV2 {
|
|
|
216
216
|
installedOrgID,
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
+
if (installedOrgID) {
|
|
220
|
+
body.installedOrgID = installedOrgID;
|
|
221
|
+
}
|
|
222
|
+
|
|
219
223
|
const uri = this.webex.meetings.preferredWebexSite
|
|
220
224
|
? `https://${this.webex.meetings.preferredWebexSite}/wbxappapi/v2/meetings/spaceInstant`
|
|
221
225
|
: '';
|
|
@@ -203,25 +203,12 @@ MeetingInfoUtil.getDestinationType = async (from) => {
|
|
|
203
203
|
return Promise.resolve(options);
|
|
204
204
|
}
|
|
205
205
|
);
|
|
206
|
-
} else if (hydraId
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
cluster: hydraId.cluster,
|
|
214
|
-
},
|
|
215
|
-
webex
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
options.destination = hydraId.destination
|
|
219
|
-
? `${serviceUrl}/conversations/${hydraId.destination}`
|
|
220
|
-
: serviceUrl;
|
|
221
|
-
} catch (e) {
|
|
222
|
-
LoggerProxy.logger.error(`Meeting-info:util#getDestinationType --> ${e}`);
|
|
223
|
-
throw e;
|
|
224
|
-
}
|
|
206
|
+
} else if (hydraId.room) {
|
|
207
|
+
LoggerProxy.logger.error(
|
|
208
|
+
`Meeting-info:util#getDestinationType --> Using the space ID as a destination is no longer supported. Please refer to the [migration guide](https://github.com/webex/webex-js-sdk/wiki/Migration-to-Unified-Space-Meetings) to migrate to use the meeting ID or SIP address.`
|
|
209
|
+
);
|
|
210
|
+
// Error code 30105 added as Space ID deprecated as of beta, Please refer migration guide.
|
|
211
|
+
throw new SpaceIDDeprecatedError();
|
|
225
212
|
} else {
|
|
226
213
|
LoggerProxy.logger.warn(`Meeting-info:util#getDestinationType --> ${meetingInfoError}`);
|
|
227
214
|
throw new ParameterError(`${meetingInfoError}`);
|
|
@@ -60,4 +60,17 @@ export default class MeetingCollection extends Collection {
|
|
|
60
60
|
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Gets the meeting that has a webrtc media connection
|
|
66
|
+
* NOTE: this function assumes there is no more than 1 such meeting
|
|
67
|
+
*
|
|
68
|
+
* @returns {Meeting} first meeting found, else undefined
|
|
69
|
+
* @public
|
|
70
|
+
* @memberof MeetingCollection
|
|
71
|
+
*/
|
|
72
|
+
public getActiveWebrtcMeeting() {
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
return find(this.meetings, (meeting) => meeting.mediaProperties.webrtcMediaConnection);
|
|
75
|
+
}
|
|
63
76
|
}
|
package/src/meetings/index.ts
CHANGED
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
50
50
|
import MeetingInfo from '../meeting-info';
|
|
51
51
|
import MeetingInfoV2 from '../meeting-info/meeting-info-v2';
|
|
52
|
-
import Meeting from '../meeting';
|
|
52
|
+
import Meeting, {CallStateForMetrics} from '../meeting';
|
|
53
53
|
import PersonalMeetingRoom from '../personal-meeting-room';
|
|
54
54
|
import Reachability from '../reachability';
|
|
55
55
|
import Request from './request';
|
|
@@ -1025,13 +1025,14 @@ export default class Meetings extends WebexPlugin {
|
|
|
1025
1025
|
}
|
|
1026
1026
|
|
|
1027
1027
|
/**
|
|
1028
|
-
* Create a meeting.
|
|
1028
|
+
* Create a meeting or return an existing meeting.
|
|
1029
1029
|
* @param {string} destination - sipURL, phonenumber, or locus object}
|
|
1030
1030
|
* @param {string} [type] - the optional specified type, such as locusId
|
|
1031
1031
|
* @param {Boolean} useRandomDelayForInfo - whether a random delay should be added to fetching meeting info
|
|
1032
1032
|
* @param {Object} infoExtraParams extra parameters to be provided when fetching meeting info
|
|
1033
|
-
* @param {string} correlationId - the optional specified correlationId
|
|
1033
|
+
* @param {string} correlationId - the optional specified correlationId (callStateForMetrics.correlationId can be provided instead)
|
|
1034
1034
|
* @param {Boolean} failOnMissingMeetingInfo - whether to throw an error if meeting info fails to fetch (for calls that are not 1:1 or content share)
|
|
1035
|
+
* @param {CallStateForMetrics} callStateForMetrics - information about call state for metrics
|
|
1035
1036
|
* @returns {Promise<Meeting>} A new Meeting.
|
|
1036
1037
|
* @public
|
|
1037
1038
|
* @memberof Meetings
|
|
@@ -1042,7 +1043,8 @@ export default class Meetings extends WebexPlugin {
|
|
|
1042
1043
|
useRandomDelayForInfo = false,
|
|
1043
1044
|
infoExtraParams = {},
|
|
1044
1045
|
correlationId: string = undefined,
|
|
1045
|
-
failOnMissingMeetingInfo = false
|
|
1046
|
+
failOnMissingMeetingInfo = false,
|
|
1047
|
+
callStateForMetrics: CallStateForMetrics = undefined
|
|
1046
1048
|
) {
|
|
1047
1049
|
// TODO: type should be from a dictionary
|
|
1048
1050
|
|
|
@@ -1050,6 +1052,10 @@ export default class Meetings extends WebexPlugin {
|
|
|
1050
1052
|
// type. This must be performed prior to determining if the meeting is
|
|
1051
1053
|
// found in the collection, as we mutate the destination for hydra person
|
|
1052
1054
|
// id values.
|
|
1055
|
+
if (correlationId) {
|
|
1056
|
+
callStateForMetrics = {...(callStateForMetrics || {}), correlationId};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1053
1059
|
return (
|
|
1054
1060
|
this.meetingInfo
|
|
1055
1061
|
.fetchInfoOptions(destination, type)
|
|
@@ -1096,7 +1102,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1096
1102
|
type,
|
|
1097
1103
|
useRandomDelayForInfo,
|
|
1098
1104
|
infoExtraParams,
|
|
1099
|
-
|
|
1105
|
+
callStateForMetrics,
|
|
1100
1106
|
failOnMissingMeetingInfo
|
|
1101
1107
|
).then((createdMeeting: any) => {
|
|
1102
1108
|
// If the meeting was successfully created.
|
|
@@ -1143,6 +1149,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1143
1149
|
return Promise.resolve(createdMeeting);
|
|
1144
1150
|
});
|
|
1145
1151
|
}
|
|
1152
|
+
meeting.setCallStateForMetrics(callStateForMetrics);
|
|
1146
1153
|
|
|
1147
1154
|
// Return the existing meeting.
|
|
1148
1155
|
return Promise.resolve(meeting);
|
|
@@ -1155,7 +1162,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1155
1162
|
* @param {String} type see create()
|
|
1156
1163
|
* @param {Boolean} useRandomDelayForInfo whether a random delay should be added to fetching meeting info
|
|
1157
1164
|
* @param {Object} infoExtraParams extra parameters to be provided when fetching meeting info
|
|
1158
|
-
* @param {
|
|
1165
|
+
* @param {CallStateForMetrics} callStateForMetrics - information about call state for metrics
|
|
1159
1166
|
* @param {Boolean} failOnMissingMeetingInfo - whether to throw an error if meeting info fails to fetch (for calls that are not 1:1 or content share)
|
|
1160
1167
|
* @returns {Promise} a new meeting instance complete with meeting info and destination
|
|
1161
1168
|
* @private
|
|
@@ -1166,7 +1173,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1166
1173
|
type: string = null,
|
|
1167
1174
|
useRandomDelayForInfo = false,
|
|
1168
1175
|
infoExtraParams = {},
|
|
1169
|
-
|
|
1176
|
+
callStateForMetrics: CallStateForMetrics = undefined,
|
|
1170
1177
|
failOnMissingMeetingInfo = false
|
|
1171
1178
|
) {
|
|
1172
1179
|
const meeting = new Meeting(
|
|
@@ -1181,7 +1188,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1181
1188
|
meetingInfoProvider: this.meetingInfo,
|
|
1182
1189
|
destination,
|
|
1183
1190
|
destinationType: type,
|
|
1184
|
-
|
|
1191
|
+
callStateForMetrics,
|
|
1185
1192
|
},
|
|
1186
1193
|
{
|
|
1187
1194
|
// @ts-ignore
|
|
@@ -1219,7 +1226,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1219
1226
|
() =>
|
|
1220
1227
|
meeting.fetchMeetingInfo({
|
|
1221
1228
|
extraParams: infoExtraParams,
|
|
1222
|
-
sendCAevents: !!correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1229
|
+
sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1223
1230
|
}),
|
|
1224
1231
|
waitingTime
|
|
1225
1232
|
);
|
|
@@ -1227,7 +1234,7 @@ export default class Meetings extends WebexPlugin {
|
|
|
1227
1234
|
} else {
|
|
1228
1235
|
await meeting.fetchMeetingInfo({
|
|
1229
1236
|
extraParams: infoExtraParams,
|
|
1230
|
-
sendCAevents: !!correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1237
|
+
sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
|
|
1231
1238
|
});
|
|
1232
1239
|
}
|
|
1233
1240
|
} catch (err) {
|
|
@@ -1467,4 +1474,15 @@ export default class Meetings extends WebexPlugin {
|
|
|
1467
1474
|
getLogger() {
|
|
1468
1475
|
return LoggerProxy.get();
|
|
1469
1476
|
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Returns the first meeting it finds that has the webrtc media connection created.
|
|
1480
|
+
* Useful for debugging in the console.
|
|
1481
|
+
*
|
|
1482
|
+
* @private
|
|
1483
|
+
* @returns {Meeting} Meeting object that has a webrtc media connection, else undefined
|
|
1484
|
+
*/
|
|
1485
|
+
getActiveWebrtcMeeting() {
|
|
1486
|
+
return this.meetingCollection.getActiveWebrtcMeeting();
|
|
1487
|
+
}
|
|
1470
1488
|
}
|
package/src/metrics/constants.ts
CHANGED
|
@@ -9,6 +9,7 @@ const BEHAVIORAL_METRICS = {
|
|
|
9
9
|
JOIN_FAILURE: 'js_sdk_join_failures',
|
|
10
10
|
ADD_MEDIA_SUCCESS: 'js_sdk_add_media_success',
|
|
11
11
|
ADD_MEDIA_FAILURE: 'js_sdk_add_media_failures',
|
|
12
|
+
ADD_MEDIA_RETRY: 'js_sdk_add_media_retry',
|
|
12
13
|
ROAP_MERCURY_EVENT_RECEIVED: 'js_sdk_roap_mercury_received',
|
|
13
14
|
CONNECTION_SUCCESS: 'js_sdk_connection_success',
|
|
14
15
|
CONNECTION_FAILURE: 'js_sdk_connection_failures',
|
|
@@ -25,9 +26,11 @@ const BEHAVIORAL_METRICS = {
|
|
|
25
26
|
MEETING_MEDIA_INACTIVE: 'js_sdk_meeting_media_inactive',
|
|
26
27
|
MEETING_RECONNECT_FAILURE: 'js_sdk_meeting_reconnect_failures',
|
|
27
28
|
MEETING_MAX_REJOIN_FAILURE: 'js_sdk_meeting_max_rejoin_failure',
|
|
29
|
+
MEETING_SHARE_SUCCESS: 'js_sdk_meeting_share_success',
|
|
28
30
|
MEETING_SHARE_FAILURE: 'js_sdk_meeting_share_failures',
|
|
29
31
|
MEETING_START_WHITEBOARD_SHARE_FAILURE: 'js_sdk_meeting_start_whiteboard_share_failures',
|
|
30
32
|
MEETING_STOP_WHITEBOARD_SHARE_FAILURE: 'js_sdk_meeting_stop_whiteboard_share_failures',
|
|
33
|
+
MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE: 'js_sdk_meeting_share_video_mute_state_change',
|
|
31
34
|
MUTE_AUDIO_FAILURE: 'js_sdk_mute_audio_failures',
|
|
32
35
|
MUTE_VIDEO_FAILURE: 'js_sdk_mute_video_failures',
|
|
33
36
|
SET_MEETING_QUALITY_FAILURE: 'js_sdk_set_meeting_quality_failures',
|
|
@@ -14,13 +14,14 @@ import {
|
|
|
14
14
|
_CALL_,
|
|
15
15
|
_LEFT_,
|
|
16
16
|
_ID_,
|
|
17
|
+
RECONNECTION_STATE,
|
|
17
18
|
} from '../constants';
|
|
18
19
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
19
|
-
import ReconnectionError from '../common/errors/reconnection';
|
|
20
20
|
import ReconnectInProgress from '../common/errors/reconnection-in-progress';
|
|
21
21
|
import Metrics from '../metrics';
|
|
22
22
|
import Meeting from '../meeting';
|
|
23
23
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
|
24
|
+
import ReconnectionError from '../common/errors/reconnection';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Used to indicate that the reconnect logic needs to be retried.
|
|
@@ -96,7 +97,7 @@ export default class ReconnectionManager {
|
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
99
|
* @instance
|
|
99
|
-
* @type {
|
|
100
|
+
* @type {RECONNECTION_STATE}
|
|
100
101
|
* @private
|
|
101
102
|
* @memberof ReconnectionManager
|
|
102
103
|
*/
|
|
@@ -227,7 +228,6 @@ export default class ReconnectionManager {
|
|
|
227
228
|
*/
|
|
228
229
|
public cleanUp() {
|
|
229
230
|
this.reset();
|
|
230
|
-
this.meeting = null;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
@@ -265,6 +265,18 @@ export default class ReconnectionManager {
|
|
|
265
265
|
return this.status === RECONNECTION.STATE.IN_PROGRESS;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Sets the reconnection status
|
|
270
|
+
*
|
|
271
|
+
* @public
|
|
272
|
+
* @param {RECONNECTION_STATE} status
|
|
273
|
+
* @memberof ReconnectionManager
|
|
274
|
+
* @returns {undefined}
|
|
275
|
+
*/
|
|
276
|
+
public setStatus(status: RECONNECTION_STATE) {
|
|
277
|
+
this.status = status;
|
|
278
|
+
}
|
|
279
|
+
|
|
268
280
|
/**
|
|
269
281
|
* @returns {Boolean}
|
|
270
282
|
* @throws {ReconnectionError}
|
|
@@ -337,73 +349,55 @@ export default class ReconnectionManager {
|
|
|
337
349
|
});
|
|
338
350
|
}
|
|
339
351
|
|
|
340
|
-
return this.executeReconnection({networkDisconnect})
|
|
341
|
-
|
|
342
|
-
LoggerProxy.logger.info('ReconnectionManager:index#reconnect --> Reconnection successful.');
|
|
352
|
+
return this.executeReconnection({networkDisconnect}).catch((reconnectError) => {
|
|
353
|
+
if (reconnectError instanceof NeedsRetryError) {
|
|
343
354
|
LoggerProxy.logger.info(
|
|
344
|
-
'ReconnectionManager:index#reconnect -->
|
|
355
|
+
'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
|
|
345
356
|
);
|
|
357
|
+
// Reset our reconnect status since we are looping back to the beginning
|
|
358
|
+
this.status = RECONNECTION.STATE.DEFAULT_STATUS;
|
|
346
359
|
|
|
347
|
-
//
|
|
348
|
-
this.
|
|
349
|
-
|
|
350
|
-
payload: {
|
|
351
|
-
recoveredBy: 'new',
|
|
352
|
-
},
|
|
353
|
-
options: {
|
|
354
|
-
meetingId: this.meeting.id,
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
})
|
|
358
|
-
.catch((reconnectError) => {
|
|
359
|
-
if (reconnectError instanceof NeedsRetryError) {
|
|
360
|
-
LoggerProxy.logger.info(
|
|
361
|
-
'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
|
|
362
|
-
);
|
|
363
|
-
// Reset our reconnect status since we are looping back to the beginning
|
|
364
|
-
this.status = RECONNECTION.STATE.DEFAULT_STATUS;
|
|
365
|
-
|
|
366
|
-
// This is a network retry, so we should not log START metrics again
|
|
367
|
-
return this.reconnect({networkDisconnect: true, networkRetry: true});
|
|
368
|
-
}
|
|
360
|
+
// This is a network retry, so we should not log START metrics again
|
|
361
|
+
return this.reconnect({networkDisconnect: true, networkRetry: true});
|
|
362
|
+
}
|
|
369
363
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
364
|
+
// Reconnect has failed
|
|
365
|
+
LoggerProxy.logger.error(
|
|
366
|
+
'ReconnectionManager:index#reconnect --> Reconnection failed.',
|
|
367
|
+
reconnectError.message
|
|
368
|
+
);
|
|
369
|
+
LoggerProxy.logger.info(
|
|
370
|
+
'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
|
|
371
|
+
);
|
|
378
372
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
373
|
+
// @ts-ignore
|
|
374
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
375
|
+
name: 'client.call.aborted',
|
|
376
|
+
payload: {
|
|
377
|
+
errors: [
|
|
378
|
+
{
|
|
379
|
+
category: 'expected',
|
|
380
|
+
errorCode: 2008,
|
|
381
|
+
fatal: true,
|
|
382
|
+
name: 'media-engine',
|
|
383
|
+
shownToUser: false,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
options: {
|
|
388
|
+
meetingId: this.meeting.id,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
if (reconnectError instanceof NeedsRejoinError) {
|
|
392
|
+
// send call aborded event with catogery as expected as we are trying to rejoin
|
|
399
393
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
394
|
+
if (this.autoRejoinEnabled) {
|
|
395
|
+
return this.rejoinMeeting(reconnectError.wasSharing);
|
|
403
396
|
}
|
|
397
|
+
}
|
|
404
398
|
|
|
405
|
-
|
|
406
|
-
|
|
399
|
+
throw reconnectError;
|
|
400
|
+
});
|
|
407
401
|
}
|
|
408
402
|
|
|
409
403
|
/**
|
|
@@ -485,14 +479,13 @@ export default class ReconnectionManager {
|
|
|
485
479
|
const media = await this.reconnectMedia();
|
|
486
480
|
|
|
487
481
|
LoggerProxy.logger.log(
|
|
488
|
-
'ReconnectionManager:index#executeReconnection -->
|
|
482
|
+
'ReconnectionManager:index#executeReconnection --> webRTC media connection renewed and local sdp offer sent'
|
|
489
483
|
);
|
|
490
|
-
this.status = RECONNECTION.STATE.COMPLETE;
|
|
491
484
|
|
|
492
485
|
return media;
|
|
493
486
|
} catch (error) {
|
|
494
487
|
LoggerProxy.logger.error(
|
|
495
|
-
'ReconnectionManager:index#executeReconnection -->
|
|
488
|
+
'ReconnectionManager:index#executeReconnection --> failed to renew webRTC media connection or initiate offer'
|
|
496
489
|
);
|
|
497
490
|
this.status = RECONNECTION.STATE.FAILURE;
|
|
498
491
|
|
|
@@ -559,9 +552,7 @@ export default class ReconnectionManager {
|
|
|
559
552
|
* @memberof ReconnectionManager
|
|
560
553
|
*/
|
|
561
554
|
async reconnectMedia() {
|
|
562
|
-
LoggerProxy.logger.log(
|
|
563
|
-
'ReconnectionManager:index#reconnectMedia --> Begin reestablishment of media'
|
|
564
|
-
);
|
|
555
|
+
LoggerProxy.logger.log('ReconnectionManager:index#reconnectMedia --> do turn discovery');
|
|
565
556
|
|
|
566
557
|
// do the TURN server discovery again and ignore reachability results since the TURN server might change
|
|
567
558
|
const turnServerResult = await this.meeting.roap.doTurnDiscovery(this.meeting, true, true);
|
|
@@ -576,6 +567,10 @@ export default class ReconnectionManager {
|
|
|
576
567
|
});
|
|
577
568
|
}
|
|
578
569
|
|
|
570
|
+
LoggerProxy.logger.log(
|
|
571
|
+
'ReconnectionManager:index#reconnectMedia --> renew webRTC media connection and send local sdp offer'
|
|
572
|
+
);
|
|
573
|
+
|
|
579
574
|
await this.meeting.mediaProperties.webrtcMediaConnection.reconnect(iceServers);
|
|
580
575
|
|
|
581
576
|
// resend media requests
|
package/src/rtcMetrics/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import {CallDiagnosticUtils} from '@webex/internal-plugin-metrics';
|
|
1
3
|
import RTC_METRICS from './constants';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -55,6 +57,9 @@ export default class RtcMetrics {
|
|
|
55
57
|
*/
|
|
56
58
|
addMetrics(data) {
|
|
57
59
|
if (data.payload.length) {
|
|
60
|
+
if (data.name === 'stats-report') {
|
|
61
|
+
data.payload = data.payload.map(this.anonymizeIp);
|
|
62
|
+
}
|
|
58
63
|
this.metricsQueue.push(data);
|
|
59
64
|
}
|
|
60
65
|
}
|
|
@@ -69,6 +74,25 @@ export default class RtcMetrics {
|
|
|
69
74
|
clearInterval(this.intervalId);
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Anonymize IP addresses.
|
|
79
|
+
*
|
|
80
|
+
* @param {array} stats - An RTCStatsReport organized into an array of strings.
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
anonymizeIp(stats: string): string {
|
|
84
|
+
const data = JSON.parse(stats);
|
|
85
|
+
// on local and remote candidates, anonymize the last 4 bits.
|
|
86
|
+
if (data.type === 'local-candidate' || data.type === 'remote-candidate') {
|
|
87
|
+
data.ip = CallDiagnosticUtils.anonymizeIPAddress(data.ip) || undefined;
|
|
88
|
+
data.address = CallDiagnosticUtils.anonymizeIPAddress(data.address) || undefined;
|
|
89
|
+
data.relatedAddress =
|
|
90
|
+
CallDiagnosticUtils.anonymizeIPAddress(data.relatedAddress) || undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return JSON.stringify(data);
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
/**
|
|
73
97
|
* Send metrics to the metrics service.
|
|
74
98
|
*
|
|
@@ -103,7 +103,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
103
103
|
this.networkQualityMonitor = networkQualityMonitor;
|
|
104
104
|
this.correlationId = config.correlationId;
|
|
105
105
|
this.mqaSentCount = -1;
|
|
106
|
-
this.lastMqaDataSent = {};
|
|
106
|
+
this.lastMqaDataSent = {resolutions: {}};
|
|
107
107
|
this.lastEmittedStartStopEvent = {};
|
|
108
108
|
this.receiveSlotCallback = receiveSlotCallback;
|
|
109
109
|
this.successfulCandidatePair = {};
|
|
@@ -152,6 +152,21 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
152
152
|
const newMqa = cloneDeep(emptyMqaInterval);
|
|
153
153
|
|
|
154
154
|
Object.keys(this.statsResults).forEach((mediaType) => {
|
|
155
|
+
if (!this.lastMqaDataSent[mediaType]) {
|
|
156
|
+
this.lastMqaDataSent[mediaType] = {};
|
|
157
|
+
this.lastMqaDataSent.resolutions[mediaType] = {};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!this.lastMqaDataSent[mediaType].send && mediaType.includes('-send')) {
|
|
161
|
+
this.lastMqaDataSent[mediaType].send = {};
|
|
162
|
+
this.lastMqaDataSent.resolutions[mediaType].send = {};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!this.lastMqaDataSent[mediaType].recv && mediaType.includes('-recv')) {
|
|
166
|
+
this.lastMqaDataSent[mediaType].recv = {};
|
|
167
|
+
this.lastMqaDataSent.resolutions[mediaType].recv = {};
|
|
168
|
+
}
|
|
169
|
+
|
|
155
170
|
if (mediaType.includes('audio-send') || mediaType.includes('audio-share-send')) {
|
|
156
171
|
const audioSender = cloneDeep(emptyAudioTransmit);
|
|
157
172
|
|
|
@@ -162,6 +177,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
162
177
|
mediaType,
|
|
163
178
|
});
|
|
164
179
|
newMqa.audioTransmit.push(audioSender);
|
|
180
|
+
|
|
181
|
+
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
165
182
|
} else if (mediaType.includes('audio-recv') || mediaType.includes('audio-share-recv')) {
|
|
166
183
|
const audioReceiver = cloneDeep(emptyAudioReceive);
|
|
167
184
|
|
|
@@ -172,6 +189,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
172
189
|
mediaType,
|
|
173
190
|
});
|
|
174
191
|
newMqa.audioReceive.push(audioReceiver);
|
|
192
|
+
|
|
193
|
+
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
175
194
|
} else if (mediaType.includes('video-send') || mediaType.includes('video-share-send')) {
|
|
176
195
|
const videoSender = cloneDeep(emptyVideoTransmit);
|
|
177
196
|
|
|
@@ -182,6 +201,11 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
182
201
|
mediaType,
|
|
183
202
|
});
|
|
184
203
|
newMqa.videoTransmit.push(videoSender);
|
|
204
|
+
|
|
205
|
+
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
206
|
+
this.lastMqaDataSent.resolutions[mediaType].send = cloneDeep(
|
|
207
|
+
this.statsResults.resolutions[mediaType].send
|
|
208
|
+
);
|
|
185
209
|
} else if (mediaType.includes('video-recv') || mediaType.includes('video-share-recv')) {
|
|
186
210
|
const videoReceiver = cloneDeep(emptyVideoReceive);
|
|
187
211
|
|
|
@@ -192,6 +216,11 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
192
216
|
mediaType,
|
|
193
217
|
});
|
|
194
218
|
newMqa.videoReceive.push(videoReceiver);
|
|
219
|
+
|
|
220
|
+
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
221
|
+
this.lastMqaDataSent.resolutions[mediaType].recv = cloneDeep(
|
|
222
|
+
this.statsResults.resolutions[mediaType].recv
|
|
223
|
+
);
|
|
195
224
|
}
|
|
196
225
|
});
|
|
197
226
|
|
|
@@ -134,9 +134,12 @@ export const getVideoReceiverMqa = ({videoReceiver, statsResults, lastMqaDataSen
|
|
|
134
134
|
const lastPacketsReceived = lastMqaDataSent[mediaType]?.[sendrecvType].totalPacketsReceived || 0;
|
|
135
135
|
const lastPacketsLost = lastMqaDataSent[mediaType]?.[sendrecvType].totalPacketsLost || 0;
|
|
136
136
|
const lastBytesReceived = lastMqaDataSent[mediaType]?.[sendrecvType].totalBytesReceived || 0;
|
|
137
|
-
const lastFramesReceived =
|
|
138
|
-
|
|
139
|
-
const
|
|
137
|
+
const lastFramesReceived =
|
|
138
|
+
lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesReceived || 0;
|
|
139
|
+
const lastFramesDecoded =
|
|
140
|
+
lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesDecoded || 0;
|
|
141
|
+
const lastFramesDropped =
|
|
142
|
+
lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesDropped || 0;
|
|
140
143
|
const lastKeyFramesDecoded = lastMqaDataSent[mediaType]?.[sendrecvType].keyFramesDecoded || 0;
|
|
141
144
|
const lastPliCount = lastMqaDataSent[mediaType]?.[sendrecvType].totalPliCount || 0;
|
|
142
145
|
|
|
@@ -190,12 +193,12 @@ export const getVideoReceiverMqa = ({videoReceiver, statsResults, lastMqaDataSen
|
|
|
190
193
|
const totalFrameDecodedInaMin =
|
|
191
194
|
statsResults.resolutions[mediaType][sendrecvType].framesDecoded - lastFramesDecoded;
|
|
192
195
|
|
|
193
|
-
videoReceiver.streams[0].common.receivedFrameRate =
|
|
194
|
-
?
|
|
195
|
-
|
|
196
|
-
videoReceiver.streams[0].common.renderedFrameRate =
|
|
197
|
-
?
|
|
198
|
-
|
|
196
|
+
videoReceiver.streams[0].common.receivedFrameRate = Math.round(
|
|
197
|
+
totalFrameReceivedInaMin ? totalFrameReceivedInaMin / 60 : 0
|
|
198
|
+
);
|
|
199
|
+
videoReceiver.streams[0].common.renderedFrameRate = Math.round(
|
|
200
|
+
totalFrameDecodedInaMin ? totalFrameDecodedInaMin / 60 : 0
|
|
201
|
+
);
|
|
199
202
|
|
|
200
203
|
videoReceiver.streams[0].common.framesDropped =
|
|
201
204
|
statsResults.resolutions[mediaType][sendrecvType].framesDropped - lastFramesDropped;
|
|
@@ -220,9 +223,9 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, m
|
|
|
220
223
|
lastMqaDataSent[mediaType]?.[sendrecvType].totalPacketsLostOnReceiver || 0;
|
|
221
224
|
const lastBytesSent = lastMqaDataSent[mediaType]?.[sendrecvType].totalBytesSent || 0;
|
|
222
225
|
const lastKeyFramesEncoded =
|
|
223
|
-
lastMqaDataSent[mediaType]?.[sendrecvType].totalKeyFramesEncoded || 0;
|
|
226
|
+
lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].totalKeyFramesEncoded || 0;
|
|
224
227
|
const lastFirCount = lastMqaDataSent[mediaType]?.[sendrecvType].totalFirCount || 0;
|
|
225
|
-
const lastFramesSent = lastMqaDataSent[mediaType]?.[sendrecvType].framesSent || 0;
|
|
228
|
+
const lastFramesSent = lastMqaDataSent.resolutions[mediaType]?.[sendrecvType].framesSent || 0;
|
|
226
229
|
const {csi} = statsResults[mediaType];
|
|
227
230
|
if (csi && !videoSender.streams[0].common.csi.includes(csi)) {
|
|
228
231
|
videoSender.streams[0].common.csi.push(csi);
|
|
@@ -280,9 +283,9 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, m
|
|
|
280
283
|
const totalFrameSentInaMin =
|
|
281
284
|
statsResults.resolutions[mediaType][sendrecvType].framesSent - (lastFramesSent || 0);
|
|
282
285
|
|
|
283
|
-
videoSender.streams[0].common.transmittedFrameRate =
|
|
284
|
-
?
|
|
285
|
-
|
|
286
|
+
videoSender.streams[0].common.transmittedFrameRate = Math.round(
|
|
287
|
+
totalFrameSentInaMin ? totalFrameSentInaMin / 60 : 0
|
|
288
|
+
);
|
|
286
289
|
videoSender.streams[0].transmittedHeight =
|
|
287
290
|
statsResults.resolutions[mediaType][sendrecvType].height || 0;
|
|
288
291
|
videoSender.streams[0].transmittedWidth =
|
|
@@ -16,16 +16,32 @@ describe('createMediaConnection', () => {
|
|
|
16
16
|
id: 'any fake track'
|
|
17
17
|
}
|
|
18
18
|
const fakeAudioStream = {
|
|
19
|
-
|
|
19
|
+
outputStream: {
|
|
20
|
+
getTracks: () => {
|
|
21
|
+
return [fakeTrack];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
20
24
|
};
|
|
21
25
|
const fakeVideoStream = {
|
|
22
|
-
|
|
26
|
+
outputStream: {
|
|
27
|
+
getTracks: () => {
|
|
28
|
+
return [fakeTrack];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
23
31
|
};
|
|
24
32
|
const fakeShareVideoStream = {
|
|
25
|
-
|
|
33
|
+
outputStream: {
|
|
34
|
+
getTracks: () => {
|
|
35
|
+
return [fakeTrack];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
26
38
|
};
|
|
27
39
|
const fakeShareAudioStream = {
|
|
28
|
-
|
|
40
|
+
outputStream: {
|
|
41
|
+
getTracks: () => {
|
|
42
|
+
return [fakeTrack];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
29
45
|
};
|
|
30
46
|
afterEach(() => {
|
|
31
47
|
sinon.restore();
|
|
@@ -67,12 +67,14 @@ describe('plugin-meetings', () => {
|
|
|
67
67
|
canShareDesktop: null,
|
|
68
68
|
canShareContent: null,
|
|
69
69
|
canTransferFile: null,
|
|
70
|
+
canChat: null,
|
|
70
71
|
canDoVideo: null,
|
|
71
72
|
canAnnotate: null,
|
|
72
73
|
canUseVoip: null,
|
|
73
74
|
supportHQV: null,
|
|
74
75
|
supportHDV: null,
|
|
75
76
|
canShareWhiteBoard: null,
|
|
77
|
+
enforceVirtualBackground: null,
|
|
76
78
|
...expected,
|
|
77
79
|
};
|
|
78
80
|
|
|
@@ -145,12 +147,14 @@ describe('plugin-meetings', () => {
|
|
|
145
147
|
'canShareDesktop',
|
|
146
148
|
'canShareContent',
|
|
147
149
|
'canTransferFile',
|
|
150
|
+
'canChat',
|
|
148
151
|
'canDoVideo',
|
|
149
152
|
'canAnnotate',
|
|
150
153
|
'canUseVoip',
|
|
151
154
|
'supportHQV',
|
|
152
155
|
'supportHDV',
|
|
153
156
|
'canShareWhiteBoard',
|
|
157
|
+
'enforceVirtualBackground',
|
|
154
158
|
].forEach((key) => {
|
|
155
159
|
it(`get and set for ${key} work as expected`, () => {
|
|
156
160
|
const inMeetingActions = new InMeetingActions();
|