@webex/plugin-meetings 2.60.1-next.13 → 2.60.1-next.14
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/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/mediaQualityMetrics/config.d.ts +103 -99
- package/dist/mediaQualityMetrics/config.js +133 -129
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/index.js +4 -2
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.d.ts +2 -0
- package/dist/meeting/request.js +4 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meetings/index.js +19 -0
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/metrics/constants.d.ts +2 -0
- package/dist/metrics/constants.js +3 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/index.js +14 -20
- package/dist/reachability/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +63 -43
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/turnDiscovery.d.ts +18 -2
- package/dist/roap/turnDiscovery.js +163 -69
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/rtcMetrics/index.d.ts +7 -0
- package/dist/rtcMetrics/index.js +38 -1
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +135 -23
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.d.ts +28 -4
- package/dist/statsAnalyzer/mqaUtil.js +278 -148
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +21 -21
- package/src/mediaQualityMetrics/config.ts +107 -107
- package/src/meeting/index.ts +2 -0
- package/src/meeting/request.ts +6 -0
- package/src/meetings/index.ts +22 -0
- package/src/meetings/util.ts +1 -1
- package/src/metrics/constants.ts +2 -0
- package/src/reachability/index.ts +0 -6
- package/src/reconnection-manager/index.ts +18 -7
- package/src/roap/turnDiscovery.ts +100 -24
- package/src/rtcMetrics/index.ts +43 -1
- package/src/statsAnalyzer/index.ts +158 -24
- package/src/statsAnalyzer/mqaUtil.ts +302 -154
- package/test/unit/spec/meeting/index.js +46 -0
- package/test/unit/spec/meeting/request.js +2 -0
- package/test/unit/spec/meetings/utils.js +35 -8
- package/test/unit/spec/reachability/index.ts +74 -0
- package/test/unit/spec/reconnection-manager/index.js +36 -1
- package/test/unit/spec/roap/turnDiscovery.ts +326 -76
- package/test/unit/spec/rtcMetrics/index.ts +32 -3
- package/test/unit/spec/stats-analyzer/index.js +439 -1
- package/test/utils/webex-test-users.js +12 -4
|
@@ -95,12 +95,6 @@ export default class Reachability {
|
|
|
95
95
|
* @memberof Reachability
|
|
96
96
|
*/
|
|
97
97
|
public async gatherReachability(): Promise<ReachabilityResults> {
|
|
98
|
-
// Remove stored reachability results to ensure no stale data
|
|
99
|
-
// @ts-ignore
|
|
100
|
-
await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorageResult);
|
|
101
|
-
// @ts-ignore
|
|
102
|
-
await this.webex.boundedStorage.del(this.namespace, REACHABILITY.localStorageJoinCookie);
|
|
103
|
-
|
|
104
98
|
// Fetch clusters and measure latency
|
|
105
99
|
try {
|
|
106
100
|
const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
|
|
@@ -349,7 +349,20 @@ export default class ReconnectionManager {
|
|
|
349
349
|
});
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
-
|
|
352
|
+
try {
|
|
353
|
+
await this.webex.meetings.startReachability();
|
|
354
|
+
} catch (err) {
|
|
355
|
+
LoggerProxy.logger.info(
|
|
356
|
+
'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ',
|
|
357
|
+
err
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const media = await this.executeReconnection({networkDisconnect});
|
|
363
|
+
|
|
364
|
+
return media;
|
|
365
|
+
} catch (reconnectError) {
|
|
353
366
|
if (reconnectError instanceof NeedsRetryError) {
|
|
354
367
|
LoggerProxy.logger.info(
|
|
355
368
|
'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
|
|
@@ -370,6 +383,7 @@ export default class ReconnectionManager {
|
|
|
370
383
|
'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
|
|
371
384
|
);
|
|
372
385
|
|
|
386
|
+
// send call aborted event with catogery as expected as we are trying to rejoin
|
|
373
387
|
// @ts-ignore
|
|
374
388
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
375
389
|
name: 'client.call.aborted',
|
|
@@ -388,16 +402,13 @@ export default class ReconnectionManager {
|
|
|
388
402
|
meetingId: this.meeting.id,
|
|
389
403
|
},
|
|
390
404
|
});
|
|
391
|
-
if (reconnectError instanceof NeedsRejoinError) {
|
|
392
|
-
// send call aborded event with catogery as expected as we are trying to rejoin
|
|
393
405
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
406
|
+
if (reconnectError instanceof NeedsRejoinError && this.autoRejoinEnabled) {
|
|
407
|
+
return this.rejoinMeeting(reconnectError.wasSharing);
|
|
397
408
|
}
|
|
398
409
|
|
|
399
410
|
throw reconnectError;
|
|
400
|
-
}
|
|
411
|
+
}
|
|
401
412
|
}
|
|
402
413
|
|
|
403
414
|
/**
|
|
@@ -56,7 +56,7 @@ export default class TurnDiscovery {
|
|
|
56
56
|
* @private
|
|
57
57
|
* @memberof Roap
|
|
58
58
|
*/
|
|
59
|
-
private waitForTurnDiscoveryResponse() {
|
|
59
|
+
private waitForTurnDiscoveryResponse(): Promise<{isOkRequired: boolean}> {
|
|
60
60
|
if (!this.defer) {
|
|
61
61
|
LoggerProxy.logger.warn(
|
|
62
62
|
'Roap:turnDiscovery#waitForTurnDiscoveryResponse --> TURN discovery is not in progress'
|
|
@@ -88,22 +88,32 @@ export default class TurnDiscovery {
|
|
|
88
88
|
* handles TURN_DISCOVERY_RESPONSE roap message
|
|
89
89
|
*
|
|
90
90
|
* @param {Object} roapMessage
|
|
91
|
+
* @param {string} from string to indicate how we got the response (used just for logging)
|
|
91
92
|
* @returns {void}
|
|
92
93
|
* @public
|
|
93
94
|
* @memberof Roap
|
|
94
95
|
*/
|
|
95
|
-
public handleTurnDiscoveryResponse(roapMessage:
|
|
96
|
-
// @ts-ignore - Fix missing type
|
|
96
|
+
public handleTurnDiscoveryResponse(roapMessage: any, from: string) {
|
|
97
97
|
const {headers} = roapMessage;
|
|
98
98
|
|
|
99
99
|
if (!this.defer) {
|
|
100
100
|
LoggerProxy.logger.warn(
|
|
101
|
-
|
|
101
|
+
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response ${from}`
|
|
102
102
|
);
|
|
103
103
|
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
if (roapMessage.messageType !== ROAP.ROAP_TYPES.TURN_DISCOVERY_RESPONSE) {
|
|
108
|
+
this.defer.reject(
|
|
109
|
+
new Error(
|
|
110
|
+
`TURN_DISCOVERY_RESPONSE ${from} has unexpected messageType: ${JSON.stringify(
|
|
111
|
+
roapMessage
|
|
112
|
+
)}`
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
107
117
|
const expectedHeaders = [
|
|
108
118
|
{headerName: 'x-cisco-turn-url', field: 'url'},
|
|
109
119
|
{headerName: 'x-cisco-turn-username', field: 'username'},
|
|
@@ -129,21 +139,39 @@ export default class TurnDiscovery {
|
|
|
129
139
|
|
|
130
140
|
if (foundHeaders !== expectedHeaders.length) {
|
|
131
141
|
LoggerProxy.logger.warn(
|
|
132
|
-
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received: ${JSON.stringify(
|
|
142
|
+
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received ${from}: ${JSON.stringify(
|
|
133
143
|
headers
|
|
134
144
|
)}`
|
|
135
145
|
);
|
|
136
146
|
this.defer.reject(
|
|
137
|
-
new Error(
|
|
147
|
+
new Error(
|
|
148
|
+
`TURN_DISCOVERY_RESPONSE ${from} missing some headers: ${JSON.stringify(headers)}`
|
|
149
|
+
)
|
|
138
150
|
);
|
|
139
151
|
} else {
|
|
140
152
|
LoggerProxy.logger.info(
|
|
141
|
-
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response, url=${this.turnInfo.url}`
|
|
153
|
+
`Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response ${from}, url=${this.turnInfo.url}`
|
|
142
154
|
);
|
|
143
|
-
|
|
155
|
+
|
|
156
|
+
this.defer.resolve({isOkRequired: !headers?.includes('noOkInTransaction')});
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
|
|
160
|
+
/**
|
|
161
|
+
* handles TURN_DISCOVERY_RESPONSE roap message that came in http response
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} roapMessage
|
|
164
|
+
* @returns {Promise}
|
|
165
|
+
* @memberof Roap
|
|
166
|
+
*/
|
|
167
|
+
private async handleTurnDiscoveryResponseInHttpResponse(
|
|
168
|
+
roapMessage: object
|
|
169
|
+
): Promise<{isOkRequired: boolean}> {
|
|
170
|
+
this.handleTurnDiscoveryResponse(roapMessage, 'in http response');
|
|
171
|
+
|
|
172
|
+
return this.defer.promise;
|
|
173
|
+
}
|
|
174
|
+
|
|
147
175
|
/**
|
|
148
176
|
* sends the TURN_DISCOVERY_REQUEST roap request
|
|
149
177
|
*
|
|
@@ -168,6 +196,7 @@ export default class TurnDiscovery {
|
|
|
168
196
|
messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,
|
|
169
197
|
version: ROAP.ROAP_VERSION,
|
|
170
198
|
seq: TURN_DISCOVERY_SEQ,
|
|
199
|
+
headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
|
|
171
200
|
};
|
|
172
201
|
|
|
173
202
|
LoggerProxy.logger.info(
|
|
@@ -186,10 +215,41 @@ export default class TurnDiscovery {
|
|
|
186
215
|
// @ts-ignore - because of meeting.webex
|
|
187
216
|
ipVersion: MeetingUtil.getIpVersion(meeting.webex),
|
|
188
217
|
})
|
|
189
|
-
.then((
|
|
218
|
+
.then((response) => {
|
|
219
|
+
const {mediaConnections} = response;
|
|
220
|
+
|
|
221
|
+
let turnDiscoveryResponse;
|
|
222
|
+
|
|
190
223
|
if (mediaConnections) {
|
|
191
224
|
meeting.updateMediaConnections(mediaConnections);
|
|
225
|
+
|
|
226
|
+
if (mediaConnections[0]?.remoteSdp) {
|
|
227
|
+
const remoteSdp = JSON.parse(mediaConnections[0].remoteSdp);
|
|
228
|
+
|
|
229
|
+
if (remoteSdp.roapMessage) {
|
|
230
|
+
// yes, it's misleading that remoteSdp actually contains a TURN discovery response, but that's how the backend works...
|
|
231
|
+
const {seq, messageType, errorType, errorCause, headers} = remoteSdp.roapMessage;
|
|
232
|
+
|
|
233
|
+
turnDiscoveryResponse = {
|
|
234
|
+
seq,
|
|
235
|
+
messageType,
|
|
236
|
+
errorType,
|
|
237
|
+
errorCause,
|
|
238
|
+
headers,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!turnDiscoveryResponse) {
|
|
245
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING, {
|
|
246
|
+
correlationId: meeting.correlationId,
|
|
247
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
248
|
+
isMultistream: meeting.isMultistream,
|
|
249
|
+
});
|
|
192
250
|
}
|
|
251
|
+
|
|
252
|
+
return turnDiscoveryResponse;
|
|
193
253
|
});
|
|
194
254
|
}
|
|
195
255
|
|
|
@@ -284,30 +344,46 @@ export default class TurnDiscovery {
|
|
|
284
344
|
};
|
|
285
345
|
}
|
|
286
346
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
.then(() => this.sendRoapOK(meeting))
|
|
290
|
-
.then(() => {
|
|
291
|
-
this.defer = undefined;
|
|
347
|
+
try {
|
|
348
|
+
const httpResponse = await this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting);
|
|
292
349
|
|
|
293
|
-
|
|
350
|
+
// if we haven't got the response over http, we need to wait for it to come over the websocket via Mercury
|
|
351
|
+
const {isOkRequired} = httpResponse
|
|
352
|
+
? await this.handleTurnDiscoveryResponseInHttpResponse(httpResponse)
|
|
353
|
+
: await this.waitForTurnDiscoveryResponse();
|
|
354
|
+
|
|
355
|
+
if (isOkRequired) {
|
|
356
|
+
await this.sendRoapOK(meeting);
|
|
294
357
|
|
|
295
|
-
return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
|
|
296
|
-
})
|
|
297
|
-
.catch((e) => {
|
|
298
|
-
// we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
|
|
299
358
|
LoggerProxy.logger.info(
|
|
300
|
-
|
|
359
|
+
'Roap:turnDiscovery#doTurnDiscovery --> TURN discovery response requires OK'
|
|
301
360
|
);
|
|
302
361
|
|
|
303
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.
|
|
362
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_REQUIRES_OK, {
|
|
304
363
|
correlation_id: meeting.correlationId,
|
|
305
364
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
306
|
-
reason: e.message,
|
|
307
|
-
stack: e.stack,
|
|
308
365
|
});
|
|
366
|
+
}
|
|
309
367
|
|
|
310
|
-
|
|
368
|
+
this.defer = undefined;
|
|
369
|
+
|
|
370
|
+
LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');
|
|
371
|
+
|
|
372
|
+
return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
|
|
373
|
+
} catch (e) {
|
|
374
|
+
// we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
|
|
375
|
+
LoggerProxy.logger.info(
|
|
376
|
+
`Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE, {
|
|
380
|
+
correlation_id: meeting.correlationId,
|
|
381
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
382
|
+
reason: e.message,
|
|
383
|
+
stack: e.stack,
|
|
311
384
|
});
|
|
385
|
+
|
|
386
|
+
return {turnServerInfo: undefined, turnDiscoverySkippedReason: undefined};
|
|
387
|
+
}
|
|
312
388
|
}
|
|
313
389
|
}
|
package/src/rtcMetrics/index.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
/* eslint-disable class-methods-use-this */
|
|
2
2
|
import {CallDiagnosticUtils} from '@webex/internal-plugin-metrics';
|
|
3
|
+
import uuid from 'uuid';
|
|
3
4
|
import RTC_METRICS from './constants';
|
|
4
5
|
|
|
6
|
+
const parseJsonPayload = (payload: any[]): any | null => {
|
|
7
|
+
try {
|
|
8
|
+
if (payload && payload[0]) {
|
|
9
|
+
return JSON.parse(payload[0]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return null;
|
|
13
|
+
} catch (_) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
5
18
|
/**
|
|
6
19
|
* Rtc Metrics
|
|
7
20
|
*/
|
|
@@ -19,6 +32,8 @@ export default class RtcMetrics {
|
|
|
19
32
|
|
|
20
33
|
correlationId: string;
|
|
21
34
|
|
|
35
|
+
connectionId: string;
|
|
36
|
+
|
|
22
37
|
/**
|
|
23
38
|
* Initialize the interval.
|
|
24
39
|
*
|
|
@@ -32,6 +47,7 @@ export default class RtcMetrics {
|
|
|
32
47
|
this.meetingId = meetingId;
|
|
33
48
|
this.webex = webex;
|
|
34
49
|
this.correlationId = correlationId;
|
|
50
|
+
this.setNewConnectionId();
|
|
35
51
|
// Send the first set of metrics at 5 seconds in the case of a user leaving the call shortly after joining.
|
|
36
52
|
setTimeout(this.sendMetricsInQueue.bind(this), 5 * 1000);
|
|
37
53
|
}
|
|
@@ -60,7 +76,23 @@ export default class RtcMetrics {
|
|
|
60
76
|
if (data.name === 'stats-report') {
|
|
61
77
|
data.payload = data.payload.map(this.anonymizeIp);
|
|
62
78
|
}
|
|
79
|
+
|
|
63
80
|
this.metricsQueue.push(data);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// If a connection fails, send the rest of the metrics in queue and get a new connection id.
|
|
84
|
+
const parsedPayload = parseJsonPayload(data.payload);
|
|
85
|
+
if (
|
|
86
|
+
data.name === 'onconnectionstatechange' &&
|
|
87
|
+
parsedPayload &&
|
|
88
|
+
parsedPayload.value === 'failed'
|
|
89
|
+
) {
|
|
90
|
+
this.sendMetricsInQueue();
|
|
91
|
+
this.setNewConnectionId();
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(e);
|
|
95
|
+
}
|
|
64
96
|
}
|
|
65
97
|
}
|
|
66
98
|
|
|
@@ -93,6 +125,15 @@ export default class RtcMetrics {
|
|
|
93
125
|
return JSON.stringify(data);
|
|
94
126
|
}
|
|
95
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Set a new connection id.
|
|
130
|
+
*
|
|
131
|
+
* @returns {void}
|
|
132
|
+
*/
|
|
133
|
+
private setNewConnectionId() {
|
|
134
|
+
this.connectionId = uuid.v4();
|
|
135
|
+
}
|
|
136
|
+
|
|
96
137
|
/**
|
|
97
138
|
* Send metrics to the metrics service.
|
|
98
139
|
*
|
|
@@ -111,10 +152,11 @@ export default class RtcMetrics {
|
|
|
111
152
|
metrics: [
|
|
112
153
|
{
|
|
113
154
|
type: 'webrtc',
|
|
114
|
-
version: '1.0
|
|
155
|
+
version: '1.1.0',
|
|
115
156
|
userId: this.webex.internal.device.userId,
|
|
116
157
|
meetingId: this.meetingId,
|
|
117
158
|
correlationId: this.correlationId,
|
|
159
|
+
connectionId: this.connectionId,
|
|
118
160
|
data: this.metricsQueue,
|
|
119
161
|
},
|
|
120
162
|
],
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
emptyMqaInterval,
|
|
19
19
|
emptyVideoReceive,
|
|
20
20
|
emptyVideoTransmit,
|
|
21
|
+
emptyAudioReceiveStream,
|
|
22
|
+
emptyAudioTransmitStream,
|
|
23
|
+
emptyVideoReceiveStream,
|
|
24
|
+
emptyVideoTransmitStream,
|
|
21
25
|
} from '../mediaQualityMetrics/config';
|
|
22
26
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
23
27
|
|
|
@@ -27,6 +31,10 @@ import {
|
|
|
27
31
|
getAudioReceiverMqa,
|
|
28
32
|
getVideoSenderMqa,
|
|
29
33
|
getVideoReceiverMqa,
|
|
34
|
+
getAudioSenderStreamMqa,
|
|
35
|
+
getAudioReceiverStreamMqa,
|
|
36
|
+
getVideoSenderStreamMqa,
|
|
37
|
+
getVideoReceiverStreamMqa,
|
|
30
38
|
} from './mqaUtil';
|
|
31
39
|
import {ReceiveSlot} from '../multistream/receiveSlot';
|
|
32
40
|
|
|
@@ -153,6 +161,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
153
161
|
sendMqaData() {
|
|
154
162
|
const newMqa = cloneDeep(emptyMqaInterval);
|
|
155
163
|
|
|
164
|
+
// Fill in empty stats items for lastMqaDataSent
|
|
156
165
|
Object.keys(this.statsResults).forEach((mediaType) => {
|
|
157
166
|
if (!this.lastMqaDataSent[mediaType]) {
|
|
158
167
|
this.lastMqaDataSent[mediaType] = {};
|
|
@@ -165,53 +174,178 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
165
174
|
if (!this.lastMqaDataSent[mediaType].recv && mediaType.includes('-recv')) {
|
|
166
175
|
this.lastMqaDataSent[mediaType].recv = {};
|
|
167
176
|
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Create stats the first level, totals for senders and receivers
|
|
180
|
+
const audioSender = cloneDeep(emptyAudioTransmit);
|
|
181
|
+
const audioShareSender = cloneDeep(emptyAudioTransmit);
|
|
182
|
+
const audioReceiver = cloneDeep(emptyAudioReceive);
|
|
183
|
+
const audioShareReceiver = cloneDeep(emptyAudioReceive);
|
|
184
|
+
const videoSender = cloneDeep(emptyVideoTransmit);
|
|
185
|
+
const videoShareSender = cloneDeep(emptyVideoTransmit);
|
|
186
|
+
const videoReceiver = cloneDeep(emptyVideoReceive);
|
|
187
|
+
const videoShareReceiver = cloneDeep(emptyVideoReceive);
|
|
188
|
+
|
|
189
|
+
getAudioSenderMqa({
|
|
190
|
+
audioSender,
|
|
191
|
+
statsResults: this.statsResults,
|
|
192
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
193
|
+
baseMediaType: 'audio-send',
|
|
194
|
+
});
|
|
195
|
+
newMqa.audioTransmit.push(audioSender);
|
|
196
|
+
|
|
197
|
+
getAudioSenderMqa({
|
|
198
|
+
audioSender: audioShareSender,
|
|
199
|
+
statsResults: this.statsResults,
|
|
200
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
201
|
+
baseMediaType: 'audio-share-send',
|
|
202
|
+
});
|
|
203
|
+
newMqa.audioTransmit.push(audioShareSender);
|
|
204
|
+
|
|
205
|
+
getAudioReceiverMqa({
|
|
206
|
+
audioReceiver,
|
|
207
|
+
statsResults: this.statsResults,
|
|
208
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
209
|
+
baseMediaType: 'audio-recv',
|
|
210
|
+
});
|
|
211
|
+
newMqa.audioReceive.push(audioReceiver);
|
|
212
|
+
|
|
213
|
+
getAudioReceiverMqa({
|
|
214
|
+
audioReceiver: audioShareReceiver,
|
|
215
|
+
statsResults: this.statsResults,
|
|
216
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
217
|
+
baseMediaType: 'audio-share-recv',
|
|
218
|
+
});
|
|
219
|
+
newMqa.audioReceive.push(audioShareReceiver);
|
|
220
|
+
|
|
221
|
+
getVideoSenderMqa({
|
|
222
|
+
videoSender,
|
|
223
|
+
statsResults: this.statsResults,
|
|
224
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
225
|
+
baseMediaType: 'video-send',
|
|
226
|
+
});
|
|
227
|
+
newMqa.videoTransmit.push(videoSender);
|
|
228
|
+
|
|
229
|
+
getVideoSenderMqa({
|
|
230
|
+
videoSender: videoShareSender,
|
|
231
|
+
statsResults: this.statsResults,
|
|
232
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
233
|
+
baseMediaType: 'video-share-send',
|
|
234
|
+
});
|
|
235
|
+
newMqa.videoTransmit.push(videoShareSender);
|
|
236
|
+
|
|
237
|
+
getVideoReceiverMqa({
|
|
238
|
+
videoReceiver,
|
|
239
|
+
statsResults: this.statsResults,
|
|
240
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
241
|
+
baseMediaType: 'video-recv',
|
|
242
|
+
});
|
|
243
|
+
newMqa.videoReceive.push(videoReceiver);
|
|
244
|
+
|
|
245
|
+
getVideoReceiverMqa({
|
|
246
|
+
videoReceiver: videoShareReceiver,
|
|
247
|
+
statsResults: this.statsResults,
|
|
248
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
249
|
+
baseMediaType: 'video-share-recv',
|
|
250
|
+
});
|
|
251
|
+
newMqa.videoReceive.push(videoShareReceiver);
|
|
252
|
+
|
|
253
|
+
// Add stats for individual streams
|
|
254
|
+
Object.keys(this.statsResults).forEach((mediaType) => {
|
|
255
|
+
if (mediaType.includes('audio-send')) {
|
|
256
|
+
const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
|
|
257
|
+
|
|
258
|
+
getAudioSenderStreamMqa({
|
|
259
|
+
audioSenderStream,
|
|
260
|
+
statsResults: this.statsResults,
|
|
261
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
262
|
+
mediaType,
|
|
263
|
+
});
|
|
264
|
+
newMqa.audioTransmit[0].streams.push(audioSenderStream);
|
|
168
265
|
|
|
169
|
-
|
|
170
|
-
|
|
266
|
+
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
267
|
+
} else if (mediaType.includes('audio-share-send')) {
|
|
268
|
+
const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
|
|
171
269
|
|
|
172
|
-
|
|
173
|
-
|
|
270
|
+
getAudioSenderStreamMqa({
|
|
271
|
+
audioSenderStream,
|
|
174
272
|
statsResults: this.statsResults,
|
|
175
273
|
lastMqaDataSent: this.lastMqaDataSent,
|
|
176
274
|
mediaType,
|
|
177
275
|
});
|
|
178
|
-
newMqa.audioTransmit.push(
|
|
276
|
+
newMqa.audioTransmit[1].streams.push(audioSenderStream);
|
|
179
277
|
|
|
180
278
|
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
181
|
-
} else if (mediaType.includes('audio-recv')
|
|
182
|
-
const
|
|
279
|
+
} else if (mediaType.includes('audio-recv')) {
|
|
280
|
+
const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
|
|
281
|
+
|
|
282
|
+
getAudioReceiverStreamMqa({
|
|
283
|
+
audioReceiverStream,
|
|
284
|
+
statsResults: this.statsResults,
|
|
285
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
286
|
+
mediaType,
|
|
287
|
+
});
|
|
288
|
+
newMqa.audioReceive[0].streams.push(audioReceiverStream);
|
|
183
289
|
|
|
184
|
-
|
|
185
|
-
|
|
290
|
+
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
291
|
+
} else if (mediaType.includes('audio-share-recv')) {
|
|
292
|
+
const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
|
|
293
|
+
|
|
294
|
+
getAudioReceiverStreamMqa({
|
|
295
|
+
audioReceiverStream,
|
|
186
296
|
statsResults: this.statsResults,
|
|
187
297
|
lastMqaDataSent: this.lastMqaDataSent,
|
|
188
298
|
mediaType,
|
|
189
299
|
});
|
|
190
|
-
newMqa.audioReceive.push(
|
|
300
|
+
newMqa.audioReceive[1].streams.push(audioReceiverStream);
|
|
191
301
|
|
|
192
302
|
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
193
|
-
} else if (mediaType.includes('video-send')
|
|
194
|
-
const
|
|
303
|
+
} else if (mediaType.includes('video-send')) {
|
|
304
|
+
const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
|
|
305
|
+
|
|
306
|
+
getVideoSenderStreamMqa({
|
|
307
|
+
videoSenderStream,
|
|
308
|
+
statsResults: this.statsResults,
|
|
309
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
310
|
+
mediaType,
|
|
311
|
+
});
|
|
312
|
+
newMqa.videoTransmit[0].streams.push(videoSenderStream);
|
|
313
|
+
|
|
314
|
+
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
315
|
+
} else if (mediaType.includes('video-share-send')) {
|
|
316
|
+
const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
|
|
195
317
|
|
|
196
|
-
|
|
197
|
-
|
|
318
|
+
getVideoSenderStreamMqa({
|
|
319
|
+
videoSenderStream,
|
|
198
320
|
statsResults: this.statsResults,
|
|
199
321
|
lastMqaDataSent: this.lastMqaDataSent,
|
|
200
322
|
mediaType,
|
|
201
323
|
});
|
|
202
|
-
newMqa.videoTransmit.push(
|
|
324
|
+
newMqa.videoTransmit[1].streams.push(videoSenderStream);
|
|
203
325
|
|
|
204
326
|
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
205
|
-
} else if (mediaType.includes('video-recv')
|
|
206
|
-
const
|
|
327
|
+
} else if (mediaType.includes('video-recv')) {
|
|
328
|
+
const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
|
|
329
|
+
|
|
330
|
+
getVideoReceiverStreamMqa({
|
|
331
|
+
videoReceiverStream,
|
|
332
|
+
statsResults: this.statsResults,
|
|
333
|
+
lastMqaDataSent: this.lastMqaDataSent,
|
|
334
|
+
mediaType,
|
|
335
|
+
});
|
|
336
|
+
newMqa.videoReceive[0].streams.push(videoReceiverStream);
|
|
337
|
+
|
|
338
|
+
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
339
|
+
} else if (mediaType.includes('video-share-recv')) {
|
|
340
|
+
const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
|
|
207
341
|
|
|
208
|
-
|
|
209
|
-
|
|
342
|
+
getVideoReceiverStreamMqa({
|
|
343
|
+
videoReceiverStream,
|
|
210
344
|
statsResults: this.statsResults,
|
|
211
345
|
lastMqaDataSent: this.lastMqaDataSent,
|
|
212
346
|
mediaType,
|
|
213
347
|
});
|
|
214
|
-
newMqa.videoReceive.push(
|
|
348
|
+
newMqa.videoReceive[1].streams.push(videoReceiverStream);
|
|
215
349
|
|
|
216
350
|
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
217
351
|
}
|
|
@@ -381,7 +515,6 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
381
515
|
this.parseCandidate(getStatsResult, type, isSender, false);
|
|
382
516
|
break;
|
|
383
517
|
case 'media-source':
|
|
384
|
-
// @ts-ignore
|
|
385
518
|
this.parseAudioSource(getStatsResult, type);
|
|
386
519
|
break;
|
|
387
520
|
default:
|
|
@@ -911,6 +1044,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
911
1044
|
if (result.bytesReceived) {
|
|
912
1045
|
let kilobytes = 0;
|
|
913
1046
|
const receiveSlot = this.receiveSlotCallback(result.ssrc);
|
|
1047
|
+
const sourceState = receiveSlot?.sourceState;
|
|
914
1048
|
const idAndCsi = receiveSlot
|
|
915
1049
|
? `id: "${receiveSlot.id || ''}"${receiveSlot.csi ? ` and csi: ${receiveSlot.csi}` : ''}`
|
|
916
1050
|
: '';
|
|
@@ -938,10 +1072,10 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
938
1072
|
this.statsResults[mediaType][sendrecvType].totalPacketsReceived = result.packetsReceived;
|
|
939
1073
|
|
|
940
1074
|
if (currentPacketsReceived === 0) {
|
|
941
|
-
if (receiveSlot) {
|
|
1075
|
+
if (receiveSlot && sourceState === 'live') {
|
|
942
1076
|
LoggerProxy.logger.info(
|
|
943
|
-
`StatsAnalyzer:index#processInboundRTPResult --> No packets received for receive slot ${idAndCsi}`,
|
|
944
|
-
|
|
1077
|
+
`StatsAnalyzer:index#processInboundRTPResult --> No packets received for receive slot ${idAndCsi}. Total packets received on slot: `,
|
|
1078
|
+
result.packetsReceived
|
|
945
1079
|
);
|
|
946
1080
|
}
|
|
947
1081
|
}
|