@webex/plugin-meetings 3.3.1 → 3.4.0
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 +7 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/constants.js +11 -4
- 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/selfUtils.js +0 -5
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +70 -15
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/index.js +12 -0
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/connectionStateHandler.js +67 -0
- package/dist/meeting/connectionStateHandler.js.map +1 -0
- package/dist/meeting/index.js +552 -357
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +7 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +6 -1
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/index.js +4 -4
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +2 -2
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/util.js +17 -17
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +16 -16
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +1 -1
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +37 -33
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +8 -0
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/util.js +3 -2
- package/dist/meetings/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +57 -0
- package/dist/metrics/index.js.map +1 -1
- package/dist/personal-meeting-room/index.js +1 -1
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/reachability/clusterReachability.js +108 -53
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +415 -56
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/constants.d.ts +11 -3
- package/dist/types/media/MediaConnectionAwaiter.d.ts +24 -4
- package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
- package/dist/types/meeting/index.d.ts +27 -7
- package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
- package/dist/types/meeting-info/index.d.ts +3 -2
- package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -2
- package/dist/types/meeting-info/util.d.ts +5 -4
- package/dist/types/meeting-info/utilv2.d.ts +3 -2
- package/dist/types/meetings/collection.d.ts +3 -2
- package/dist/types/meetings/index.d.ts +4 -3
- package/dist/types/meetings/meetings.types.d.ts +9 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/metrics/index.d.ts +15 -0
- package/dist/types/reachability/clusterReachability.d.ts +31 -3
- package/dist/types/reachability/index.d.ts +93 -2
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -23
- package/src/breakouts/index.ts +7 -1
- package/src/constants.ts +13 -17
- package/src/locus-info/selfUtils.ts +0 -5
- package/src/media/MediaConnectionAwaiter.ts +89 -14
- package/src/media/index.ts +13 -0
- package/src/meeting/connectionStateHandler.ts +65 -0
- package/src/meeting/index.ts +526 -292
- package/src/meeting/locusMediaRequest.ts +5 -0
- package/src/meeting/muteState.ts +6 -1
- package/src/meeting/util.ts +1 -0
- package/src/meeting-info/index.ts +9 -6
- package/src/meeting-info/meeting-info-v2.ts +4 -4
- package/src/meeting-info/util.ts +23 -28
- package/src/meeting-info/utilv2.ts +18 -24
- package/src/meetings/collection.ts +3 -3
- package/src/meetings/index.ts +39 -40
- package/src/meetings/meetings.types.ts +11 -0
- package/src/meetings/util.ts +5 -4
- package/src/metrics/constants.ts +1 -0
- package/src/metrics/index.ts +44 -0
- package/src/personal-meeting-room/index.ts +2 -2
- package/src/reachability/clusterReachability.ts +86 -25
- package/src/reachability/index.ts +316 -27
- package/test/unit/spec/breakouts/index.ts +51 -32
- package/test/unit/spec/locus-info/selfUtils.js +25 -23
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +131 -32
- package/test/unit/spec/media/index.ts +42 -27
- package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
- package/test/unit/spec/meeting/index.js +758 -179
- package/test/unit/spec/meeting/locusMediaRequest.ts +7 -0
- package/test/unit/spec/meeting/muteState.js +24 -0
- package/test/unit/spec/meeting-info/index.js +4 -4
- package/test/unit/spec/meeting-info/meetinginfov2.js +24 -28
- package/test/unit/spec/meeting-info/request.js +2 -2
- package/test/unit/spec/meeting-info/utilv2.js +41 -49
- package/test/unit/spec/meetings/index.js +14 -0
- package/test/unit/spec/metrics/index.js +126 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -2
- package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -2
- package/test/unit/spec/reachability/clusterReachability.ts +116 -22
- package/test/unit/spec/reachability/index.ts +1153 -84
- package/test/unit/spec/rtcMetrics/index.ts +1 -0
- package/dist/mediaQualityMetrics/config.js +0 -321
- package/dist/mediaQualityMetrics/config.js.map +0 -1
- package/dist/statsAnalyzer/global.js +0 -44
- package/dist/statsAnalyzer/global.js.map +0 -1
- package/dist/statsAnalyzer/index.js +0 -1072
- package/dist/statsAnalyzer/index.js.map +0 -1
- package/dist/statsAnalyzer/mqaUtil.js +0 -368
- package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
- package/dist/types/mediaQualityMetrics/config.d.ts +0 -247
- package/dist/types/statsAnalyzer/global.d.ts +0 -36
- package/dist/types/statsAnalyzer/index.d.ts +0 -217
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
- package/src/mediaQualityMetrics/config.ts +0 -255
- package/src/statsAnalyzer/global.ts +0 -37
- package/src/statsAnalyzer/index.ts +0 -1318
- package/src/statsAnalyzer/mqaUtil.ts +0 -463
- package/test/unit/spec/stats-analyzer/index.js +0 -1819
|
@@ -1,1318 +0,0 @@
|
|
|
1
|
-
/* eslint-disable prefer-destructuring */
|
|
2
|
-
|
|
3
|
-
import {cloneDeep, isEmpty} from 'lodash';
|
|
4
|
-
import {ConnectionState} from '@webex/internal-media-core';
|
|
5
|
-
|
|
6
|
-
import EventsScope from '../common/events/events-scope';
|
|
7
|
-
import {
|
|
8
|
-
DEFAULT_GET_STATS_FILTER,
|
|
9
|
-
STATS,
|
|
10
|
-
MQA_INTERVAL,
|
|
11
|
-
NETWORK_TYPE,
|
|
12
|
-
MEDIA_DEVICES,
|
|
13
|
-
_UNKNOWN_,
|
|
14
|
-
} from '../constants';
|
|
15
|
-
import {
|
|
16
|
-
emptyAudioReceive,
|
|
17
|
-
emptyAudioTransmit,
|
|
18
|
-
emptyMqaInterval,
|
|
19
|
-
emptyVideoReceive,
|
|
20
|
-
emptyVideoTransmit,
|
|
21
|
-
emptyAudioReceiveStream,
|
|
22
|
-
emptyAudioTransmitStream,
|
|
23
|
-
emptyVideoReceiveStream,
|
|
24
|
-
emptyVideoTransmitStream,
|
|
25
|
-
} from '../mediaQualityMetrics/config';
|
|
26
|
-
import LoggerProxy from '../common/logs/logger-proxy';
|
|
27
|
-
|
|
28
|
-
import defaultStats from './global';
|
|
29
|
-
import {
|
|
30
|
-
getAudioSenderMqa,
|
|
31
|
-
getAudioReceiverMqa,
|
|
32
|
-
getVideoSenderMqa,
|
|
33
|
-
getVideoReceiverMqa,
|
|
34
|
-
getAudioSenderStreamMqa,
|
|
35
|
-
getAudioReceiverStreamMqa,
|
|
36
|
-
getVideoSenderStreamMqa,
|
|
37
|
-
getVideoReceiverStreamMqa,
|
|
38
|
-
} from './mqaUtil';
|
|
39
|
-
import {ReceiveSlot} from '../multistream/receiveSlot';
|
|
40
|
-
|
|
41
|
-
export const EVENTS = {
|
|
42
|
-
MEDIA_QUALITY: 'MEDIA_QUALITY',
|
|
43
|
-
LOCAL_MEDIA_STARTED: 'LOCAL_MEDIA_STARTED',
|
|
44
|
-
LOCAL_MEDIA_STOPPED: 'LOCAL_MEDIA_STOPPED',
|
|
45
|
-
REMOTE_MEDIA_STARTED: 'REMOTE_MEDIA_STARTED',
|
|
46
|
-
REMOTE_MEDIA_STOPPED: 'REMOTE_MEDIA_STOPPED',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const emptySender = {
|
|
50
|
-
trackLabel: '',
|
|
51
|
-
maxPacketLossRatio: 0,
|
|
52
|
-
availableBandwidth: 0,
|
|
53
|
-
bytesSent: 0,
|
|
54
|
-
meanRemoteJitter: [],
|
|
55
|
-
meanRoundTripTime: [],
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const emptyReceiver = {
|
|
59
|
-
availableBandwidth: 0,
|
|
60
|
-
bytesReceived: 0,
|
|
61
|
-
meanRtpJitter: [],
|
|
62
|
-
meanRoundTripTime: [],
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
type ReceiveSlotCallback = (csi: number) => ReceiveSlot | undefined;
|
|
66
|
-
type MediaStatus = {
|
|
67
|
-
actual?: any;
|
|
68
|
-
expected?: any;
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* Stats Analyzer class that will emit events based on detected quality
|
|
72
|
-
*
|
|
73
|
-
* @export
|
|
74
|
-
* @class StatsAnalyzer
|
|
75
|
-
* @extends {EventsScope}
|
|
76
|
-
*/
|
|
77
|
-
export class StatsAnalyzer extends EventsScope {
|
|
78
|
-
config: any;
|
|
79
|
-
correlationId: any;
|
|
80
|
-
lastEmittedStartStopEvent: any;
|
|
81
|
-
lastMqaDataSent: any;
|
|
82
|
-
lastStatsResults: any;
|
|
83
|
-
meetingMediaStatus: any;
|
|
84
|
-
mqaInterval: NodeJS.Timeout;
|
|
85
|
-
mqaSentCount: any;
|
|
86
|
-
networkQualityMonitor: any;
|
|
87
|
-
mediaConnection: any;
|
|
88
|
-
statsInterval: NodeJS.Timeout;
|
|
89
|
-
statsResults: any;
|
|
90
|
-
statsStarted: any;
|
|
91
|
-
successfulCandidatePair: any;
|
|
92
|
-
localIpAddress: string; // Returns the local IP address for diagnostics. this is the local IP of the interface used for the current media connection a host can have many local Ip Addresses
|
|
93
|
-
receiveSlotCallback: ReceiveSlotCallback;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Creates a new instance of StatsAnalyzer
|
|
97
|
-
* @constructor
|
|
98
|
-
* @public
|
|
99
|
-
* @param {Object} config SDK Configuration Object
|
|
100
|
-
* @param {Function} receiveSlotCallback Callback used to access receive slots.
|
|
101
|
-
* @param {Object} networkQualityMonitor class for assessing network characteristics (jitter, packetLoss, latency)
|
|
102
|
-
* @param {Object} statsResults Default properties for stats
|
|
103
|
-
*/
|
|
104
|
-
constructor(
|
|
105
|
-
config: any,
|
|
106
|
-
receiveSlotCallback: ReceiveSlotCallback = () => undefined,
|
|
107
|
-
networkQualityMonitor: object = {},
|
|
108
|
-
statsResults: object = defaultStats
|
|
109
|
-
) {
|
|
110
|
-
super();
|
|
111
|
-
this.statsStarted = false;
|
|
112
|
-
this.statsResults = statsResults;
|
|
113
|
-
this.lastStatsResults = null;
|
|
114
|
-
this.config = config;
|
|
115
|
-
this.networkQualityMonitor = networkQualityMonitor;
|
|
116
|
-
this.correlationId = config.correlationId;
|
|
117
|
-
this.mqaSentCount = -1;
|
|
118
|
-
this.lastMqaDataSent = {};
|
|
119
|
-
this.lastEmittedStartStopEvent = {};
|
|
120
|
-
this.receiveSlotCallback = receiveSlotCallback;
|
|
121
|
-
this.successfulCandidatePair = {};
|
|
122
|
-
this.localIpAddress = '';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Resets cumulative stats arrays.
|
|
127
|
-
*
|
|
128
|
-
* @public
|
|
129
|
-
* @memberof StatsAnalyzer
|
|
130
|
-
* @returns {void}
|
|
131
|
-
*/
|
|
132
|
-
resetStatsResults() {
|
|
133
|
-
Object.keys(this.statsResults).forEach((mediaType) => {
|
|
134
|
-
if (mediaType.includes('recv')) {
|
|
135
|
-
this.statsResults[mediaType].recv.meanRtpJitter = [];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (mediaType.includes('send')) {
|
|
139
|
-
this.statsResults[mediaType].send.meanRemoteJitter = [];
|
|
140
|
-
this.statsResults[mediaType].send.meanRoundTripTime = [];
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* sets mediaStatus status for analyzing metrics
|
|
147
|
-
*
|
|
148
|
-
* @public
|
|
149
|
-
* @param {Object} status for the audio and video
|
|
150
|
-
* @memberof StatsAnalyzer
|
|
151
|
-
* @returns {void}
|
|
152
|
-
*/
|
|
153
|
-
public updateMediaStatus(status: MediaStatus) {
|
|
154
|
-
this.meetingMediaStatus = {
|
|
155
|
-
actual: {
|
|
156
|
-
...this.meetingMediaStatus?.actual,
|
|
157
|
-
...status?.actual,
|
|
158
|
-
},
|
|
159
|
-
expected: {
|
|
160
|
-
...this.meetingMediaStatus?.expected,
|
|
161
|
-
...status?.expected,
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* captures MQA data from media connection
|
|
168
|
-
*
|
|
169
|
-
* @public
|
|
170
|
-
* @memberof StatsAnalyzer
|
|
171
|
-
* @returns {void}
|
|
172
|
-
*/
|
|
173
|
-
sendMqaData() {
|
|
174
|
-
const newMqa = cloneDeep(emptyMqaInterval);
|
|
175
|
-
|
|
176
|
-
// Fill in empty stats items for lastMqaDataSent
|
|
177
|
-
Object.keys(this.statsResults).forEach((mediaType) => {
|
|
178
|
-
if (!this.lastMqaDataSent[mediaType]) {
|
|
179
|
-
this.lastMqaDataSent[mediaType] = {};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!this.lastMqaDataSent[mediaType].send && mediaType.includes('-send')) {
|
|
183
|
-
this.lastMqaDataSent[mediaType].send = {};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (!this.lastMqaDataSent[mediaType].recv && mediaType.includes('-recv')) {
|
|
187
|
-
this.lastMqaDataSent[mediaType].recv = {};
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Create stats the first level, totals for senders and receivers
|
|
192
|
-
const audioSender = cloneDeep(emptyAudioTransmit);
|
|
193
|
-
const audioShareSender = cloneDeep(emptyAudioTransmit);
|
|
194
|
-
const audioReceiver = cloneDeep(emptyAudioReceive);
|
|
195
|
-
const audioShareReceiver = cloneDeep(emptyAudioReceive);
|
|
196
|
-
const videoSender = cloneDeep(emptyVideoTransmit);
|
|
197
|
-
const videoShareSender = cloneDeep(emptyVideoTransmit);
|
|
198
|
-
const videoReceiver = cloneDeep(emptyVideoReceive);
|
|
199
|
-
const videoShareReceiver = cloneDeep(emptyVideoReceive);
|
|
200
|
-
|
|
201
|
-
getAudioSenderMqa({
|
|
202
|
-
audioSender,
|
|
203
|
-
statsResults: this.statsResults,
|
|
204
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
205
|
-
baseMediaType: 'audio-send',
|
|
206
|
-
});
|
|
207
|
-
newMqa.audioTransmit.push(audioSender);
|
|
208
|
-
|
|
209
|
-
getAudioSenderMqa({
|
|
210
|
-
audioSender: audioShareSender,
|
|
211
|
-
statsResults: this.statsResults,
|
|
212
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
213
|
-
baseMediaType: 'audio-share-send',
|
|
214
|
-
});
|
|
215
|
-
newMqa.audioTransmit.push(audioShareSender);
|
|
216
|
-
|
|
217
|
-
getAudioReceiverMqa({
|
|
218
|
-
audioReceiver,
|
|
219
|
-
statsResults: this.statsResults,
|
|
220
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
221
|
-
baseMediaType: 'audio-recv',
|
|
222
|
-
});
|
|
223
|
-
newMqa.audioReceive.push(audioReceiver);
|
|
224
|
-
|
|
225
|
-
getAudioReceiverMqa({
|
|
226
|
-
audioReceiver: audioShareReceiver,
|
|
227
|
-
statsResults: this.statsResults,
|
|
228
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
229
|
-
baseMediaType: 'audio-share-recv',
|
|
230
|
-
});
|
|
231
|
-
newMqa.audioReceive.push(audioShareReceiver);
|
|
232
|
-
|
|
233
|
-
getVideoSenderMqa({
|
|
234
|
-
videoSender,
|
|
235
|
-
statsResults: this.statsResults,
|
|
236
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
237
|
-
baseMediaType: 'video-send',
|
|
238
|
-
});
|
|
239
|
-
newMqa.videoTransmit.push(videoSender);
|
|
240
|
-
|
|
241
|
-
getVideoSenderMqa({
|
|
242
|
-
videoSender: videoShareSender,
|
|
243
|
-
statsResults: this.statsResults,
|
|
244
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
245
|
-
baseMediaType: 'video-share-send',
|
|
246
|
-
});
|
|
247
|
-
newMqa.videoTransmit.push(videoShareSender);
|
|
248
|
-
|
|
249
|
-
getVideoReceiverMqa({
|
|
250
|
-
videoReceiver,
|
|
251
|
-
statsResults: this.statsResults,
|
|
252
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
253
|
-
baseMediaType: 'video-recv',
|
|
254
|
-
});
|
|
255
|
-
newMqa.videoReceive.push(videoReceiver);
|
|
256
|
-
|
|
257
|
-
getVideoReceiverMqa({
|
|
258
|
-
videoReceiver: videoShareReceiver,
|
|
259
|
-
statsResults: this.statsResults,
|
|
260
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
261
|
-
baseMediaType: 'video-share-recv',
|
|
262
|
-
});
|
|
263
|
-
newMqa.videoReceive.push(videoShareReceiver);
|
|
264
|
-
|
|
265
|
-
// Add stats for individual streams
|
|
266
|
-
Object.keys(this.statsResults).forEach((mediaType) => {
|
|
267
|
-
if (mediaType.startsWith('audio-send')) {
|
|
268
|
-
const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
|
|
269
|
-
|
|
270
|
-
getAudioSenderStreamMqa({
|
|
271
|
-
audioSenderStream,
|
|
272
|
-
statsResults: this.statsResults,
|
|
273
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
274
|
-
mediaType,
|
|
275
|
-
});
|
|
276
|
-
newMqa.audioTransmit[0].streams.push(audioSenderStream);
|
|
277
|
-
|
|
278
|
-
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
279
|
-
} else if (mediaType.startsWith('audio-share-send')) {
|
|
280
|
-
const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
|
|
281
|
-
|
|
282
|
-
getAudioSenderStreamMqa({
|
|
283
|
-
audioSenderStream,
|
|
284
|
-
statsResults: this.statsResults,
|
|
285
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
286
|
-
mediaType,
|
|
287
|
-
});
|
|
288
|
-
newMqa.audioTransmit[1].streams.push(audioSenderStream);
|
|
289
|
-
|
|
290
|
-
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
291
|
-
} else if (mediaType.startsWith('audio-recv')) {
|
|
292
|
-
const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
|
|
293
|
-
|
|
294
|
-
getAudioReceiverStreamMqa({
|
|
295
|
-
audioReceiverStream,
|
|
296
|
-
statsResults: this.statsResults,
|
|
297
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
298
|
-
mediaType,
|
|
299
|
-
});
|
|
300
|
-
newMqa.audioReceive[0].streams.push(audioReceiverStream);
|
|
301
|
-
|
|
302
|
-
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
303
|
-
} else if (mediaType.startsWith('audio-share-recv')) {
|
|
304
|
-
const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
|
|
305
|
-
|
|
306
|
-
getAudioReceiverStreamMqa({
|
|
307
|
-
audioReceiverStream,
|
|
308
|
-
statsResults: this.statsResults,
|
|
309
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
310
|
-
mediaType,
|
|
311
|
-
});
|
|
312
|
-
newMqa.audioReceive[1].streams.push(audioReceiverStream);
|
|
313
|
-
|
|
314
|
-
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
315
|
-
} else if (mediaType.startsWith('video-send-layer')) {
|
|
316
|
-
// We only want the stream-specific stats we get with video-send-layer-0, video-send-layer-1, etc.
|
|
317
|
-
const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
|
|
318
|
-
|
|
319
|
-
getVideoSenderStreamMqa({
|
|
320
|
-
videoSenderStream,
|
|
321
|
-
statsResults: this.statsResults,
|
|
322
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
323
|
-
mediaType,
|
|
324
|
-
});
|
|
325
|
-
newMqa.videoTransmit[0].streams.push(videoSenderStream);
|
|
326
|
-
|
|
327
|
-
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
328
|
-
} else if (mediaType.startsWith('video-share-send')) {
|
|
329
|
-
const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
|
|
330
|
-
|
|
331
|
-
getVideoSenderStreamMqa({
|
|
332
|
-
videoSenderStream,
|
|
333
|
-
statsResults: this.statsResults,
|
|
334
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
335
|
-
mediaType,
|
|
336
|
-
});
|
|
337
|
-
newMqa.videoTransmit[1].streams.push(videoSenderStream);
|
|
338
|
-
|
|
339
|
-
this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
|
|
340
|
-
} else if (mediaType.startsWith('video-recv')) {
|
|
341
|
-
const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
|
|
342
|
-
|
|
343
|
-
getVideoReceiverStreamMqa({
|
|
344
|
-
videoReceiverStream,
|
|
345
|
-
statsResults: this.statsResults,
|
|
346
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
347
|
-
mediaType,
|
|
348
|
-
});
|
|
349
|
-
newMqa.videoReceive[0].streams.push(videoReceiverStream);
|
|
350
|
-
|
|
351
|
-
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
352
|
-
} else if (mediaType.startsWith('video-share-recv')) {
|
|
353
|
-
const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
|
|
354
|
-
|
|
355
|
-
getVideoReceiverStreamMqa({
|
|
356
|
-
videoReceiverStream,
|
|
357
|
-
statsResults: this.statsResults,
|
|
358
|
-
lastMqaDataSent: this.lastMqaDataSent,
|
|
359
|
-
mediaType,
|
|
360
|
-
});
|
|
361
|
-
newMqa.videoReceive[1].streams.push(videoReceiverStream);
|
|
362
|
-
|
|
363
|
-
this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
newMqa.intervalMetadata.peerReflexiveIP = this.statsResults.connectionType.local.ipAddress;
|
|
368
|
-
|
|
369
|
-
// Adding peripheral information
|
|
370
|
-
newMqa.intervalMetadata.peripherals.push({information: _UNKNOWN_, name: MEDIA_DEVICES.SPEAKER});
|
|
371
|
-
if (this.statsResults['audio-send']) {
|
|
372
|
-
newMqa.intervalMetadata.peripherals.push({
|
|
373
|
-
information: this.statsResults['audio-send'].trackLabel || _UNKNOWN_,
|
|
374
|
-
name: MEDIA_DEVICES.MICROPHONE,
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const existingVideoSender = Object.keys(this.statsResults).find((item) =>
|
|
379
|
-
item.includes('video-send')
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
if (existingVideoSender) {
|
|
383
|
-
newMqa.intervalMetadata.peripherals.push({
|
|
384
|
-
information: this.statsResults[existingVideoSender].trackLabel || _UNKNOWN_,
|
|
385
|
-
name: MEDIA_DEVICES.CAMERA,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
newMqa.networkType = this.statsResults.connectionType.local.networkType;
|
|
390
|
-
|
|
391
|
-
this.mqaSentCount += 1;
|
|
392
|
-
|
|
393
|
-
newMqa.intervalNumber = this.mqaSentCount;
|
|
394
|
-
|
|
395
|
-
this.resetStatsResults();
|
|
396
|
-
|
|
397
|
-
this.emit(
|
|
398
|
-
{
|
|
399
|
-
file: 'statsAnalyzer',
|
|
400
|
-
function: 'sendMqaData',
|
|
401
|
-
},
|
|
402
|
-
EVENTS.MEDIA_QUALITY,
|
|
403
|
-
{
|
|
404
|
-
data: newMqa,
|
|
405
|
-
// @ts-ignore
|
|
406
|
-
networkType: newMqa.networkType,
|
|
407
|
-
}
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* updated the media connection when changed
|
|
413
|
-
*
|
|
414
|
-
* @private
|
|
415
|
-
* @memberof StatsAnalyzer
|
|
416
|
-
* @param {RoapMediaConnection} mediaConnection
|
|
417
|
-
* @returns {void}
|
|
418
|
-
*/
|
|
419
|
-
updateMediaConnection(mediaConnection: any) {
|
|
420
|
-
this.mediaConnection = mediaConnection;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Returns the local IP address for diagnostics.
|
|
425
|
-
* this is the local IP of the interface used for the current media connection
|
|
426
|
-
* a host can have many local Ip Addresses
|
|
427
|
-
* @returns {string | undefined} The local IP address.
|
|
428
|
-
*/
|
|
429
|
-
getLocalIpAddress(): string {
|
|
430
|
-
return this.localIpAddress;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Starts the stats analyzer on interval
|
|
435
|
-
*
|
|
436
|
-
* @public
|
|
437
|
-
* @memberof StatsAnalyzer
|
|
438
|
-
* @param {RoapMediaConnection} mediaConnection
|
|
439
|
-
* @returns {Promise}
|
|
440
|
-
*/
|
|
441
|
-
public startAnalyzer(mediaConnection: any) {
|
|
442
|
-
if (!this.statsStarted) {
|
|
443
|
-
this.statsStarted = true;
|
|
444
|
-
this.mediaConnection = mediaConnection;
|
|
445
|
-
|
|
446
|
-
return this.getStatsAndParse().then(() => {
|
|
447
|
-
this.statsInterval = setInterval(() => {
|
|
448
|
-
this.getStatsAndParse();
|
|
449
|
-
}, this.config.analyzerInterval);
|
|
450
|
-
// Trigger initial fetch
|
|
451
|
-
this.sendMqaData();
|
|
452
|
-
this.mqaInterval = setInterval(() => {
|
|
453
|
-
this.sendMqaData();
|
|
454
|
-
}, MQA_INTERVAL);
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return Promise.resolve();
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Cleans up the analyzer when done
|
|
463
|
-
*
|
|
464
|
-
* @public
|
|
465
|
-
* @memberof StatsAnalyzer
|
|
466
|
-
* @returns {void}
|
|
467
|
-
*/
|
|
468
|
-
public stopAnalyzer() {
|
|
469
|
-
const sendOneLastMqa = this.mqaInterval && this.statsInterval;
|
|
470
|
-
|
|
471
|
-
if (this.statsInterval) {
|
|
472
|
-
clearInterval(this.statsInterval);
|
|
473
|
-
this.statsInterval = undefined;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (this.mqaInterval) {
|
|
477
|
-
clearInterval(this.mqaInterval);
|
|
478
|
-
this.mqaInterval = undefined;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (sendOneLastMqa) {
|
|
482
|
-
return this.getStatsAndParse().then(() => {
|
|
483
|
-
this.sendMqaData();
|
|
484
|
-
this.mediaConnection = null;
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return Promise.resolve();
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Parse a single result of get stats
|
|
493
|
-
*
|
|
494
|
-
* @private
|
|
495
|
-
* @param {*} getStatsResult
|
|
496
|
-
* @param {String} type
|
|
497
|
-
* @param {boolean} isSender
|
|
498
|
-
* @returns {void}
|
|
499
|
-
* @memberof StatsAnalyzer
|
|
500
|
-
*/
|
|
501
|
-
private parseGetStatsResult(getStatsResult: any, type: string, isSender: boolean) {
|
|
502
|
-
if (!getStatsResult) {
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Generate empty stats results
|
|
507
|
-
if (!this.statsResults[type]) {
|
|
508
|
-
this.statsResults[type] = {};
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (isSender && !this.statsResults[type].send) {
|
|
512
|
-
this.statsResults[type].send = cloneDeep(emptySender);
|
|
513
|
-
} else if (!isSender && !this.statsResults[type].recv) {
|
|
514
|
-
this.statsResults[type].recv = cloneDeep(emptyReceiver);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
switch (getStatsResult.type) {
|
|
518
|
-
case 'outbound-rtp':
|
|
519
|
-
this.processOutboundRTPResult(getStatsResult, type);
|
|
520
|
-
break;
|
|
521
|
-
case 'inbound-rtp':
|
|
522
|
-
this.processInboundRTPResult(getStatsResult, type);
|
|
523
|
-
break;
|
|
524
|
-
case 'remote-inbound-rtp':
|
|
525
|
-
case 'remote-outbound-rtp':
|
|
526
|
-
this.compareSentAndReceived(getStatsResult, type);
|
|
527
|
-
break;
|
|
528
|
-
case 'remotecandidate':
|
|
529
|
-
case 'remote-candidate':
|
|
530
|
-
this.parseCandidate(getStatsResult, type, isSender, true);
|
|
531
|
-
break;
|
|
532
|
-
case 'local-candidate':
|
|
533
|
-
this.parseCandidate(getStatsResult, type, isSender, false);
|
|
534
|
-
break;
|
|
535
|
-
case 'media-source':
|
|
536
|
-
this.parseAudioSource(getStatsResult, type);
|
|
537
|
-
break;
|
|
538
|
-
default:
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Filters the get stats results for types
|
|
545
|
-
* @private
|
|
546
|
-
* @param {Array} statsItem
|
|
547
|
-
* @param {String} type
|
|
548
|
-
* @param {boolean} isSender
|
|
549
|
-
* @returns {void}
|
|
550
|
-
*/
|
|
551
|
-
filterAndParseGetStatsResults(statsItem: any, type: string, isSender: boolean) {
|
|
552
|
-
const {types} = DEFAULT_GET_STATS_FILTER;
|
|
553
|
-
|
|
554
|
-
// get the successful candidate pair before parsing stats.
|
|
555
|
-
statsItem.report.forEach((report) => {
|
|
556
|
-
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
|
|
557
|
-
this.successfulCandidatePair = report;
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
let videoSenderIndex = 0;
|
|
562
|
-
statsItem.report.forEach((result) => {
|
|
563
|
-
if (types.includes(result.type)) {
|
|
564
|
-
// if the video sender has multiple streams in the report, it is a new stream object.
|
|
565
|
-
if (type === 'video-send' && result.type === 'outbound-rtp') {
|
|
566
|
-
const newType = `video-send-layer-${videoSenderIndex}`;
|
|
567
|
-
this.parseGetStatsResult(result, newType, isSender);
|
|
568
|
-
videoSenderIndex += 1;
|
|
569
|
-
|
|
570
|
-
this.statsResults[newType].direction = statsItem.currentDirection;
|
|
571
|
-
this.statsResults[newType].trackLabel = statsItem.localTrackLabel;
|
|
572
|
-
this.statsResults[newType].csi = statsItem.csi;
|
|
573
|
-
} else {
|
|
574
|
-
this.parseGetStatsResult(result, type, isSender);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
if (this.statsResults[type]) {
|
|
580
|
-
this.statsResults[type].direction = statsItem.currentDirection;
|
|
581
|
-
this.statsResults[type].trackLabel = statsItem.localTrackLabel;
|
|
582
|
-
this.statsResults[type].csi = statsItem.csi;
|
|
583
|
-
this.extractAndSetLocalIpAddressInfoForDiagnostics(
|
|
584
|
-
this.successfulCandidatePair?.localCandidateId,
|
|
585
|
-
this.statsResults?.candidates
|
|
586
|
-
);
|
|
587
|
-
// reset the successful candidate pair.
|
|
588
|
-
this.successfulCandidatePair = {};
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* parse the audio
|
|
594
|
-
* @param {String} result
|
|
595
|
-
* @param {boolean} type
|
|
596
|
-
* @returns {void}
|
|
597
|
-
*/
|
|
598
|
-
parseAudioSource(result: any, type: any) {
|
|
599
|
-
if (!result) {
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
if (type.includes('audio-send')) {
|
|
604
|
-
this.statsResults[type].send.audioLevel = result.audioLevel;
|
|
605
|
-
this.statsResults[type].send.totalAudioEnergy = result.totalAudioEnergy;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* emits started/stopped events for local/remote media by checking
|
|
611
|
-
* if given values are increasing or not. The previousValue, currentValue
|
|
612
|
-
* params can be any numerical value like number of receive packets or
|
|
613
|
-
* decoded frames, etc.
|
|
614
|
-
*
|
|
615
|
-
* @private
|
|
616
|
-
* @param {string} mediaType
|
|
617
|
-
* @param {number} previousValue - value to compare
|
|
618
|
-
* @param {number} currentValue - value to compare (must be same type of value as previousValue)
|
|
619
|
-
* @param {boolean} isLocal - true if stats are for local media being sent out, false for remote media being received
|
|
620
|
-
* @memberof StatsAnalyzer
|
|
621
|
-
* @returns {void}
|
|
622
|
-
*/
|
|
623
|
-
emitStartStopEvents = (
|
|
624
|
-
mediaType: string,
|
|
625
|
-
previousValue: number,
|
|
626
|
-
currentValue: number,
|
|
627
|
-
isLocal: boolean
|
|
628
|
-
) => {
|
|
629
|
-
if (mediaType !== 'audio' && mediaType !== 'video' && mediaType !== 'share') {
|
|
630
|
-
throw new Error(`Unsupported mediaType: ${mediaType}`);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// eslint-disable-next-line no-param-reassign
|
|
634
|
-
if (previousValue === undefined) previousValue = 0;
|
|
635
|
-
// eslint-disable-next-line no-param-reassign
|
|
636
|
-
if (currentValue === undefined) currentValue = 0;
|
|
637
|
-
|
|
638
|
-
if (!this.lastEmittedStartStopEvent[mediaType]) {
|
|
639
|
-
this.lastEmittedStartStopEvent[mediaType] = {};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const lastEmittedEvent = isLocal
|
|
643
|
-
? this.lastEmittedStartStopEvent[mediaType].local
|
|
644
|
-
: this.lastEmittedStartStopEvent[mediaType].remote;
|
|
645
|
-
|
|
646
|
-
let newEvent;
|
|
647
|
-
|
|
648
|
-
if (currentValue - previousValue > 0) {
|
|
649
|
-
newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STARTED : EVENTS.REMOTE_MEDIA_STARTED;
|
|
650
|
-
} else if (currentValue === previousValue && currentValue > 0) {
|
|
651
|
-
newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STOPPED : EVENTS.REMOTE_MEDIA_STOPPED;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
if (newEvent && lastEmittedEvent !== newEvent) {
|
|
655
|
-
if (isLocal) {
|
|
656
|
-
this.lastEmittedStartStopEvent[mediaType].local = newEvent;
|
|
657
|
-
} else {
|
|
658
|
-
this.lastEmittedStartStopEvent[mediaType].remote = newEvent;
|
|
659
|
-
}
|
|
660
|
-
this.emit(
|
|
661
|
-
{
|
|
662
|
-
file: 'statsAnalyzer/index',
|
|
663
|
-
function: 'compareLastStatsResult',
|
|
664
|
-
},
|
|
665
|
-
newEvent,
|
|
666
|
-
{
|
|
667
|
-
type: mediaType,
|
|
668
|
-
}
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* compares current and previous stats to check if packets are not sent
|
|
675
|
-
*
|
|
676
|
-
* @private
|
|
677
|
-
* @memberof StatsAnalyzer
|
|
678
|
-
* @returns {void}
|
|
679
|
-
*/
|
|
680
|
-
private compareLastStatsResult() {
|
|
681
|
-
if (this.lastStatsResults !== null && this.meetingMediaStatus) {
|
|
682
|
-
const getCurrentStatsTotals = (keyPrefix: string, value: string): number =>
|
|
683
|
-
Object.keys(this.statsResults)
|
|
684
|
-
.filter((key) => key.startsWith(keyPrefix))
|
|
685
|
-
.reduce(
|
|
686
|
-
(prev, cur) =>
|
|
687
|
-
prev +
|
|
688
|
-
(this.statsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] || 0),
|
|
689
|
-
0
|
|
690
|
-
);
|
|
691
|
-
|
|
692
|
-
const getPreviousStatsTotals = (keyPrefix: string, value: string): number =>
|
|
693
|
-
Object.keys(this.statsResults)
|
|
694
|
-
.filter((key) => key.startsWith(keyPrefix))
|
|
695
|
-
.reduce(
|
|
696
|
-
(prev, cur) =>
|
|
697
|
-
prev +
|
|
698
|
-
(this.lastStatsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] ||
|
|
699
|
-
0),
|
|
700
|
-
0
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
// Audio Transmit
|
|
704
|
-
if (this.lastStatsResults['audio-send']) {
|
|
705
|
-
// compare audio stats sent
|
|
706
|
-
// NOTE: relies on there being only one sender.
|
|
707
|
-
const currentStats = this.statsResults['audio-send'].send;
|
|
708
|
-
const previousStats = this.lastStatsResults['audio-send'].send;
|
|
709
|
-
|
|
710
|
-
if (
|
|
711
|
-
(this.meetingMediaStatus.expected.sendAudio &&
|
|
712
|
-
currentStats.totalPacketsSent === previousStats.totalPacketsSent) ||
|
|
713
|
-
currentStats.totalPacketsSent === 0
|
|
714
|
-
) {
|
|
715
|
-
LoggerProxy.logger.info(
|
|
716
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent`,
|
|
717
|
-
currentStats.totalPacketsSent
|
|
718
|
-
);
|
|
719
|
-
} else {
|
|
720
|
-
if (
|
|
721
|
-
(this.meetingMediaStatus.expected.sendAudio &&
|
|
722
|
-
currentStats.totalAudioEnergy === previousStats.totalAudioEnergy) ||
|
|
723
|
-
currentStats.totalAudioEnergy === 0
|
|
724
|
-
) {
|
|
725
|
-
LoggerProxy.logger.info(
|
|
726
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio Energy present`,
|
|
727
|
-
currentStats.totalAudioEnergy
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (this.meetingMediaStatus.expected.sendAudio && currentStats.audioLevel === 0) {
|
|
732
|
-
LoggerProxy.logger.info(
|
|
733
|
-
`StatsAnalyzer:index#compareLastStatsResult --> audio level is 0 for the user`
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
this.emitStartStopEvents(
|
|
739
|
-
'audio',
|
|
740
|
-
previousStats.totalPacketsSent,
|
|
741
|
-
currentStats.totalPacketsSent,
|
|
742
|
-
true
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Audio Receive
|
|
747
|
-
const currentAudioPacketsReceived = getCurrentStatsTotals(
|
|
748
|
-
'audio-recv',
|
|
749
|
-
'totalPacketsReceived'
|
|
750
|
-
);
|
|
751
|
-
const previousAudioPacketsReceived = getPreviousStatsTotals(
|
|
752
|
-
'audio-recv',
|
|
753
|
-
'totalPacketsReceived'
|
|
754
|
-
);
|
|
755
|
-
|
|
756
|
-
this.emitStartStopEvents(
|
|
757
|
-
'audio',
|
|
758
|
-
previousAudioPacketsReceived,
|
|
759
|
-
currentAudioPacketsReceived,
|
|
760
|
-
false
|
|
761
|
-
);
|
|
762
|
-
|
|
763
|
-
const currentTotalPacketsSent = getCurrentStatsTotals('video-send', 'totalPacketsSent');
|
|
764
|
-
const previousTotalPacketsSent = getPreviousStatsTotals('video-send', 'totalPacketsSent');
|
|
765
|
-
|
|
766
|
-
const currentFramesEncoded = getCurrentStatsTotals('video-send', 'framesEncoded');
|
|
767
|
-
const previousFramesEncoded = getPreviousStatsTotals('video-send', 'framesEncoded');
|
|
768
|
-
|
|
769
|
-
const currentFramesSent = getCurrentStatsTotals('video-send', 'framesSent');
|
|
770
|
-
const previousFramesSent = getPreviousStatsTotals('video-send', 'framesSent');
|
|
771
|
-
|
|
772
|
-
const doesVideoSendExist = Object.keys(this.lastStatsResults).some((item) =>
|
|
773
|
-
item.includes('video-send')
|
|
774
|
-
);
|
|
775
|
-
|
|
776
|
-
// Video Transmit
|
|
777
|
-
if (doesVideoSendExist) {
|
|
778
|
-
// compare video stats sent
|
|
779
|
-
|
|
780
|
-
if (
|
|
781
|
-
this.meetingMediaStatus.expected.sendVideo &&
|
|
782
|
-
(currentTotalPacketsSent === previousTotalPacketsSent || currentTotalPacketsSent === 0)
|
|
783
|
-
) {
|
|
784
|
-
LoggerProxy.logger.info(
|
|
785
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent`,
|
|
786
|
-
currentTotalPacketsSent
|
|
787
|
-
);
|
|
788
|
-
} else {
|
|
789
|
-
if (
|
|
790
|
-
this.meetingMediaStatus.expected.sendVideo &&
|
|
791
|
-
(currentFramesEncoded === previousFramesEncoded || currentFramesEncoded === 0)
|
|
792
|
-
) {
|
|
793
|
-
LoggerProxy.logger.info(
|
|
794
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded`,
|
|
795
|
-
currentFramesEncoded
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
if (
|
|
800
|
-
this.meetingMediaStatus.expected.sendVideo &&
|
|
801
|
-
(currentFramesSent === previousFramesSent || currentFramesSent === 0)
|
|
802
|
-
) {
|
|
803
|
-
LoggerProxy.logger.info(
|
|
804
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent`,
|
|
805
|
-
currentFramesSent
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
this.emitStartStopEvents('video', previousFramesSent, currentFramesSent, true);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// Video Receive
|
|
814
|
-
const currentVideoFramesDecoded = getCurrentStatsTotals('video-recv', 'framesDecoded');
|
|
815
|
-
const previousVideoFramesDecoded = getPreviousStatsTotals('video-recv', 'framesDecoded');
|
|
816
|
-
|
|
817
|
-
this.emitStartStopEvents(
|
|
818
|
-
'video',
|
|
819
|
-
previousVideoFramesDecoded,
|
|
820
|
-
currentVideoFramesDecoded,
|
|
821
|
-
false
|
|
822
|
-
);
|
|
823
|
-
|
|
824
|
-
// Share Transmit
|
|
825
|
-
if (this.lastStatsResults['video-share-send']) {
|
|
826
|
-
// compare share stats sent
|
|
827
|
-
|
|
828
|
-
const currentStats = this.statsResults['video-share-send'].send;
|
|
829
|
-
const previousStats = this.lastStatsResults['video-share-send'].send;
|
|
830
|
-
|
|
831
|
-
if (
|
|
832
|
-
this.meetingMediaStatus.expected.sendShare &&
|
|
833
|
-
(currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
|
|
834
|
-
currentStats.totalPacketsSent === 0)
|
|
835
|
-
) {
|
|
836
|
-
LoggerProxy.logger.info(
|
|
837
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent`,
|
|
838
|
-
currentStats.totalPacketsSent
|
|
839
|
-
);
|
|
840
|
-
} else {
|
|
841
|
-
if (
|
|
842
|
-
this.meetingMediaStatus.expected.sendShare &&
|
|
843
|
-
(currentStats.framesEncoded === previousStats.framesEncoded ||
|
|
844
|
-
currentStats.framesEncoded === 0)
|
|
845
|
-
) {
|
|
846
|
-
LoggerProxy.logger.info(
|
|
847
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames getting encoded`,
|
|
848
|
-
currentStats.framesEncoded
|
|
849
|
-
);
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
if (
|
|
853
|
-
this.meetingMediaStatus.expected.sendShare &&
|
|
854
|
-
(this.statsResults['video-share-send'].send.framesSent ===
|
|
855
|
-
this.lastStatsResults['video-share-send'].send.framesSent ||
|
|
856
|
-
this.statsResults['video-share-send'].send.framesSent === 0)
|
|
857
|
-
) {
|
|
858
|
-
LoggerProxy.logger.info(
|
|
859
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames sent`,
|
|
860
|
-
this.statsResults['video-share-send'].send.framesSent
|
|
861
|
-
);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
this.emitStartStopEvents('share', previousStats.framesSent, currentStats.framesSent, true);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// Share receive
|
|
869
|
-
const currentShareFramesDecoded = getCurrentStatsTotals('video-share-recv', 'framesDecoded');
|
|
870
|
-
const previousShareFramesDecoded = getPreviousStatsTotals(
|
|
871
|
-
'video-share-recv',
|
|
872
|
-
'framesDecoded'
|
|
873
|
-
);
|
|
874
|
-
|
|
875
|
-
this.emitStartStopEvents(
|
|
876
|
-
'share',
|
|
877
|
-
previousShareFramesDecoded,
|
|
878
|
-
currentShareFramesDecoded,
|
|
879
|
-
false
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
/**
|
|
885
|
-
* Does a `getStats` on all the transceivers and parses the results
|
|
886
|
-
*
|
|
887
|
-
* @private
|
|
888
|
-
* @memberof StatsAnalyzer
|
|
889
|
-
* @returns {Promise}
|
|
890
|
-
*/
|
|
891
|
-
private getStatsAndParse() {
|
|
892
|
-
if (!this.mediaConnection) {
|
|
893
|
-
return Promise.resolve();
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
if (
|
|
897
|
-
this.mediaConnection &&
|
|
898
|
-
this.mediaConnection.getConnectionState() === ConnectionState.Failed
|
|
899
|
-
) {
|
|
900
|
-
LoggerProxy.logger.trace(
|
|
901
|
-
'StatsAnalyzer:index#getStatsAndParse --> media connection is in failed state'
|
|
902
|
-
);
|
|
903
|
-
|
|
904
|
-
return Promise.resolve();
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
LoggerProxy.logger.trace('StatsAnalyzer:index#getStatsAndParse --> Collecting Stats');
|
|
908
|
-
|
|
909
|
-
return this.mediaConnection.getTransceiverStats().then((transceiverStats) => {
|
|
910
|
-
transceiverStats.video.receivers.forEach((receiver, i) =>
|
|
911
|
-
this.filterAndParseGetStatsResults(receiver, `video-recv-${i}`, false)
|
|
912
|
-
);
|
|
913
|
-
transceiverStats.audio.receivers.forEach((receiver, i) =>
|
|
914
|
-
this.filterAndParseGetStatsResults(receiver, `audio-recv-${i}`, false)
|
|
915
|
-
);
|
|
916
|
-
transceiverStats.screenShareVideo.receivers.forEach((receiver, i) =>
|
|
917
|
-
this.filterAndParseGetStatsResults(receiver, `video-share-recv-${i}`, false)
|
|
918
|
-
);
|
|
919
|
-
transceiverStats.screenShareAudio.receivers.forEach((receiver, i) =>
|
|
920
|
-
this.filterAndParseGetStatsResults(receiver, `audio-share-recv-${i}`, false)
|
|
921
|
-
);
|
|
922
|
-
|
|
923
|
-
transceiverStats.video.senders.forEach((sender, i) => {
|
|
924
|
-
if (i > 0) {
|
|
925
|
-
throw new Error('Stats Analyzer does not support multiple senders.');
|
|
926
|
-
}
|
|
927
|
-
this.filterAndParseGetStatsResults(sender, 'video-send', true);
|
|
928
|
-
});
|
|
929
|
-
transceiverStats.audio.senders.forEach((sender, i) => {
|
|
930
|
-
if (i > 0) {
|
|
931
|
-
throw new Error('Stats Analyzer does not support multiple senders.');
|
|
932
|
-
}
|
|
933
|
-
this.filterAndParseGetStatsResults(sender, 'audio-send', true);
|
|
934
|
-
});
|
|
935
|
-
transceiverStats.screenShareVideo.senders.forEach((sender, i) => {
|
|
936
|
-
if (i > 0) {
|
|
937
|
-
throw new Error('Stats Analyzer does not support multiple senders.');
|
|
938
|
-
}
|
|
939
|
-
this.filterAndParseGetStatsResults(sender, 'video-share-send', true);
|
|
940
|
-
});
|
|
941
|
-
transceiverStats.screenShareAudio.senders.forEach((sender, i) => {
|
|
942
|
-
if (i > 0) {
|
|
943
|
-
throw new Error('Stats Analyzer does not support multiple senders.');
|
|
944
|
-
}
|
|
945
|
-
this.filterAndParseGetStatsResults(sender, 'audio-share-send', true);
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
this.compareLastStatsResult();
|
|
949
|
-
|
|
950
|
-
// Save the last results to compare with the current
|
|
951
|
-
// DO Deep copy, for some reason it takes the reference all the time rather then old value set
|
|
952
|
-
this.lastStatsResults = JSON.parse(JSON.stringify(this.statsResults));
|
|
953
|
-
|
|
954
|
-
LoggerProxy.logger.trace(
|
|
955
|
-
'StatsAnalyzer:index#getStatsAndParse --> Finished Collecting Stats'
|
|
956
|
-
);
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* Processes OutboundRTP stats result and stores
|
|
962
|
-
* @private
|
|
963
|
-
* @param {*} result
|
|
964
|
-
* @param {*} mediaType
|
|
965
|
-
* @returns {void}
|
|
966
|
-
*/
|
|
967
|
-
private processOutboundRTPResult(result: any, mediaType: any) {
|
|
968
|
-
const sendrecvType = STATS.SEND_DIRECTION;
|
|
969
|
-
|
|
970
|
-
if (result.bytesSent) {
|
|
971
|
-
const kilobytes = 0;
|
|
972
|
-
|
|
973
|
-
if (result.frameWidth && result.frameHeight) {
|
|
974
|
-
this.statsResults[mediaType][sendrecvType].width = result.frameWidth;
|
|
975
|
-
this.statsResults[mediaType][sendrecvType].height = result.frameHeight;
|
|
976
|
-
this.statsResults[mediaType][sendrecvType].framesSent = result.framesSent;
|
|
977
|
-
this.statsResults[mediaType][sendrecvType].hugeFramesSent = result.hugeFramesSent;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
this.statsResults[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
|
|
981
|
-
|
|
982
|
-
this.statsResults[mediaType][sendrecvType].framesEncoded = result.framesEncoded;
|
|
983
|
-
this.statsResults[mediaType][sendrecvType].keyFramesEncoded = result.keyFramesEncoded;
|
|
984
|
-
this.statsResults[mediaType][sendrecvType].packetsSent = result.packetsSent;
|
|
985
|
-
|
|
986
|
-
// Data saved to send MQA metrics
|
|
987
|
-
|
|
988
|
-
this.statsResults[mediaType][sendrecvType].totalKeyFramesEncoded = result.keyFramesEncoded;
|
|
989
|
-
this.statsResults[mediaType][sendrecvType].totalNackCount = result.nackCount;
|
|
990
|
-
this.statsResults[mediaType][sendrecvType].totalPliCount = result.pliCount;
|
|
991
|
-
this.statsResults[mediaType][sendrecvType].totalPacketsSent = result.packetsSent;
|
|
992
|
-
this.statsResults[mediaType][sendrecvType].totalFirCount = result.firCount;
|
|
993
|
-
this.statsResults[mediaType][sendrecvType].framesSent = result.framesSent;
|
|
994
|
-
this.statsResults[mediaType][sendrecvType].framesEncoded = result.framesEncoded;
|
|
995
|
-
this.statsResults[mediaType][sendrecvType].encoderImplementation =
|
|
996
|
-
result.encoderImplementation;
|
|
997
|
-
this.statsResults[mediaType][sendrecvType].qualityLimitationReason =
|
|
998
|
-
result.qualityLimitationReason;
|
|
999
|
-
this.statsResults[mediaType][sendrecvType].qualityLimitationResolutionChanges =
|
|
1000
|
-
result.qualityLimitationResolutionChanges;
|
|
1001
|
-
this.statsResults[mediaType][sendrecvType].totalRtxPacketsSent =
|
|
1002
|
-
result.retransmittedPacketsSent;
|
|
1003
|
-
this.statsResults[mediaType][sendrecvType].totalRtxBytesSent = result.retransmittedBytesSent;
|
|
1004
|
-
this.statsResults[mediaType][sendrecvType].totalBytesSent = result.bytesSent;
|
|
1005
|
-
this.statsResults[mediaType][sendrecvType].headerBytesSent = result.headerBytesSent;
|
|
1006
|
-
this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
|
|
1007
|
-
this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
/**
|
|
1012
|
-
* Processes InboundRTP stats result and stores
|
|
1013
|
-
* @private
|
|
1014
|
-
* @param {*} result
|
|
1015
|
-
* @param {*} mediaType
|
|
1016
|
-
* @returns {void}
|
|
1017
|
-
*/
|
|
1018
|
-
private processInboundRTPResult(result: any, mediaType: any) {
|
|
1019
|
-
const sendrecvType = STATS.RECEIVE_DIRECTION;
|
|
1020
|
-
|
|
1021
|
-
if (result.bytesReceived) {
|
|
1022
|
-
let kilobytes = 0;
|
|
1023
|
-
const receiveSlot = this.receiveSlotCallback(result.ssrc);
|
|
1024
|
-
const sourceState = receiveSlot?.sourceState;
|
|
1025
|
-
const idAndCsi = receiveSlot
|
|
1026
|
-
? `id: "${receiveSlot.id || ''}"${receiveSlot.csi ? ` and csi: ${receiveSlot.csi}` : ''}`
|
|
1027
|
-
: '';
|
|
1028
|
-
|
|
1029
|
-
const bytes =
|
|
1030
|
-
result.bytesReceived - this.statsResults[mediaType][sendrecvType].totalBytesReceived;
|
|
1031
|
-
|
|
1032
|
-
kilobytes = bytes / 1024;
|
|
1033
|
-
this.statsResults[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
|
|
1034
|
-
|
|
1035
|
-
let currentPacketsLost =
|
|
1036
|
-
result.packetsLost - this.statsResults[mediaType][sendrecvType].totalPacketsLost;
|
|
1037
|
-
if (currentPacketsLost < 0) {
|
|
1038
|
-
currentPacketsLost = 0;
|
|
1039
|
-
}
|
|
1040
|
-
const packetsReceivedDiff =
|
|
1041
|
-
result.packetsReceived - this.statsResults[mediaType][sendrecvType].totalPacketsReceived;
|
|
1042
|
-
this.statsResults[mediaType][sendrecvType].totalPacketsReceived = result.packetsReceived;
|
|
1043
|
-
|
|
1044
|
-
if (packetsReceivedDiff === 0) {
|
|
1045
|
-
if (receiveSlot && sourceState === 'live') {
|
|
1046
|
-
LoggerProxy.logger.info(
|
|
1047
|
-
`StatsAnalyzer:index#processInboundRTPResult --> No packets received for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total packets received on slot: `,
|
|
1048
|
-
result.packetsReceived
|
|
1049
|
-
);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
if (mediaType.startsWith('video') || mediaType.startsWith('share')) {
|
|
1054
|
-
const videoFramesReceivedDiff =
|
|
1055
|
-
result.framesReceived - this.statsResults[mediaType][sendrecvType].framesReceived;
|
|
1056
|
-
|
|
1057
|
-
if (videoFramesReceivedDiff === 0) {
|
|
1058
|
-
if (receiveSlot && sourceState === 'live') {
|
|
1059
|
-
LoggerProxy.logger.info(
|
|
1060
|
-
`StatsAnalyzer:index#processInboundRTPResult --> No frames received for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames received on slot: `,
|
|
1061
|
-
result.framesReceived
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
const videoFramesDecodedDiff =
|
|
1067
|
-
result.framesDecoded - this.statsResults[mediaType][sendrecvType].framesDecoded;
|
|
1068
|
-
|
|
1069
|
-
if (videoFramesDecodedDiff === 0) {
|
|
1070
|
-
if (receiveSlot && sourceState === 'live') {
|
|
1071
|
-
LoggerProxy.logger.info(
|
|
1072
|
-
`StatsAnalyzer:index#processInboundRTPResult --> No frames decoded for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames decoded on slot: `,
|
|
1073
|
-
result.framesDecoded
|
|
1074
|
-
);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
const videoFramesDroppedDiff =
|
|
1079
|
-
result.framesDropped - this.statsResults[mediaType][sendrecvType].framesDropped;
|
|
1080
|
-
|
|
1081
|
-
if (videoFramesDroppedDiff > 10) {
|
|
1082
|
-
if (receiveSlot && sourceState === 'live') {
|
|
1083
|
-
LoggerProxy.logger.info(
|
|
1084
|
-
`StatsAnalyzer:index#processInboundRTPResult --> Frames dropped for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames dropped on slot: `,
|
|
1085
|
-
result.framesDropped
|
|
1086
|
-
);
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
// Check the over all packet Lost ratio
|
|
1092
|
-
this.statsResults[mediaType][sendrecvType].currentPacketLossRatio =
|
|
1093
|
-
currentPacketsLost > 0
|
|
1094
|
-
? currentPacketsLost / (packetsReceivedDiff + currentPacketsLost)
|
|
1095
|
-
: 0;
|
|
1096
|
-
if (this.statsResults[mediaType][sendrecvType].currentPacketLossRatio > 3) {
|
|
1097
|
-
LoggerProxy.logger.info(
|
|
1098
|
-
`StatsAnalyzer:index#processInboundRTPResult --> Packets getting lost from the receiver with slot ${idAndCsi}`,
|
|
1099
|
-
this.statsResults[mediaType][sendrecvType].currentPacketLossRatio
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
if (result.frameWidth && result.frameHeight) {
|
|
1104
|
-
this.statsResults[mediaType][sendrecvType].width = result.frameWidth;
|
|
1105
|
-
this.statsResults[mediaType][sendrecvType].height = result.frameHeight;
|
|
1106
|
-
this.statsResults[mediaType][sendrecvType].framesReceived = result.framesReceived;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// TODO: check the packet loss value is negative values here
|
|
1110
|
-
|
|
1111
|
-
if (result.packetsLost) {
|
|
1112
|
-
this.statsResults[mediaType][sendrecvType].totalPacketsLost =
|
|
1113
|
-
result.packetsLost > 0 ? result.packetsLost : -result.packetsLost;
|
|
1114
|
-
} else {
|
|
1115
|
-
this.statsResults[mediaType][sendrecvType].totalPacketsLost = 0;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
this.statsResults[mediaType][sendrecvType].lastPacketReceivedTimestamp =
|
|
1119
|
-
result.lastPacketReceivedTimestamp;
|
|
1120
|
-
this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
|
|
1121
|
-
this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
|
|
1122
|
-
|
|
1123
|
-
// From Thin
|
|
1124
|
-
this.statsResults[mediaType][sendrecvType].totalNackCount = result.nackCount;
|
|
1125
|
-
this.statsResults[mediaType][sendrecvType].totalPliCount = result.pliCount;
|
|
1126
|
-
this.statsResults[mediaType][sendrecvType].framesDecoded = result.framesDecoded;
|
|
1127
|
-
this.statsResults[mediaType][sendrecvType].keyFramesDecoded = result.keyFramesDecoded;
|
|
1128
|
-
this.statsResults[mediaType][sendrecvType].framesDropped = result.framesDropped;
|
|
1129
|
-
|
|
1130
|
-
this.statsResults[mediaType][sendrecvType].decoderImplementation =
|
|
1131
|
-
result.decoderImplementation;
|
|
1132
|
-
this.statsResults[mediaType][sendrecvType].totalPacketsReceived = result.packetsReceived;
|
|
1133
|
-
|
|
1134
|
-
this.statsResults[mediaType][sendrecvType].fecPacketsDiscarded = result.fecPacketsDiscarded;
|
|
1135
|
-
this.statsResults[mediaType][sendrecvType].fecPacketsReceived = result.fecPacketsReceived;
|
|
1136
|
-
this.statsResults[mediaType][sendrecvType].totalBytesReceived = result.bytesReceived;
|
|
1137
|
-
this.statsResults[mediaType][sendrecvType].headerBytesReceived = result.headerBytesReceived;
|
|
1138
|
-
this.statsResults[mediaType][sendrecvType].totalRtxPacketsReceived =
|
|
1139
|
-
result.retransmittedPacketsReceived;
|
|
1140
|
-
this.statsResults[mediaType][sendrecvType].totalRtxBytesReceived =
|
|
1141
|
-
result.retransmittedBytesReceived;
|
|
1142
|
-
|
|
1143
|
-
this.statsResults[mediaType][sendrecvType].meanRtpJitter.push(result.jitter);
|
|
1144
|
-
|
|
1145
|
-
// Audio stats
|
|
1146
|
-
|
|
1147
|
-
this.statsResults[mediaType][sendrecvType].audioLevel = result.audioLevel;
|
|
1148
|
-
this.statsResults[mediaType][sendrecvType].totalAudioEnergy = result.totalAudioEnergy;
|
|
1149
|
-
this.statsResults[mediaType][sendrecvType].totalSamplesReceived =
|
|
1150
|
-
result.totalSamplesReceived || 0;
|
|
1151
|
-
this.statsResults[mediaType][sendrecvType].totalSamplesDecoded =
|
|
1152
|
-
result.totalSamplesDecoded || 0;
|
|
1153
|
-
this.statsResults[mediaType][sendrecvType].concealedSamples = result.concealedSamples || 0;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
/**
|
|
1158
|
-
* extracts the local Ip address from the statsResult object by looking at stats results candidates
|
|
1159
|
-
* and matches that ID with the successful candidate pair. It looks at the type of local candidate it is
|
|
1160
|
-
* and then extracts the IP address from the relatedAddress or address property based on conditions known in webrtc
|
|
1161
|
-
* note, there are known incompatibilities and it is possible for this to set undefined, or for the IP address to be the public IP address
|
|
1162
|
-
* for example, firefox does not set the relayProtocol, and if the user is behind a NAT it might be the public IP
|
|
1163
|
-
* @private
|
|
1164
|
-
* @param {string} successfulCandidatePairId - The ID of the successful candidate pair.
|
|
1165
|
-
* @param {Object} candidates - the stats result candidates
|
|
1166
|
-
* @returns {void}
|
|
1167
|
-
*/
|
|
1168
|
-
extractAndSetLocalIpAddressInfoForDiagnostics = (
|
|
1169
|
-
successfulCandidatePairId: string,
|
|
1170
|
-
candidates: {[key: string]: Record<string, unknown>}
|
|
1171
|
-
) => {
|
|
1172
|
-
let newIpAddress = '';
|
|
1173
|
-
if (successfulCandidatePairId && !isEmpty(candidates)) {
|
|
1174
|
-
const localCandidate = candidates[successfulCandidatePairId];
|
|
1175
|
-
if (localCandidate) {
|
|
1176
|
-
if (localCandidate.candidateType === 'host') {
|
|
1177
|
-
// if it's a host candidate, use the address property - it will be the local IP
|
|
1178
|
-
newIpAddress = `${localCandidate.address}`;
|
|
1179
|
-
} else if (localCandidate.candidateType === 'prflx') {
|
|
1180
|
-
// if it's a peer reflexive candidate and we're not using a relay (there is no relayProtocol set)
|
|
1181
|
-
// then look at the relatedAddress - it will be the local
|
|
1182
|
-
//
|
|
1183
|
-
// Firefox doesn't populate the relayProtocol property
|
|
1184
|
-
if (!localCandidate.relayProtocol) {
|
|
1185
|
-
newIpAddress = `${localCandidate.relatedAddress}`;
|
|
1186
|
-
} else {
|
|
1187
|
-
// if it's a peer reflexive candidate and we are using a relay -
|
|
1188
|
-
// in that case the relatedAddress will be the IP of the TURN server (Linus),
|
|
1189
|
-
// so we can only look at the address, but it might be local IP or public IP,
|
|
1190
|
-
// depending on if the user is behind a NAT or not
|
|
1191
|
-
newIpAddress = `${localCandidate.address}`;
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
this.localIpAddress = newIpAddress;
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
* Processes remote and local candidate result and stores
|
|
1201
|
-
* @private
|
|
1202
|
-
* @param {*} result
|
|
1203
|
-
* @param {*} type
|
|
1204
|
-
* @param {boolean} isSender
|
|
1205
|
-
* @param {boolean} isRemote
|
|
1206
|
-
*
|
|
1207
|
-
* @returns {void}
|
|
1208
|
-
*/
|
|
1209
|
-
parseCandidate = (result: any, type: any, isSender: boolean, isRemote: boolean) => {
|
|
1210
|
-
if (!result || !result.id) {
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// We only care about the successful local candidate
|
|
1215
|
-
if (this.successfulCandidatePair?.localCandidateId !== result.id) {
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
let transport;
|
|
1220
|
-
if (result.relayProtocol) {
|
|
1221
|
-
transport = result.relayProtocol.toUpperCase();
|
|
1222
|
-
} else if (result.protocol) {
|
|
1223
|
-
transport = result.protocol.toUpperCase();
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
const sendRecvType = isSender ? STATS.SEND_DIRECTION : STATS.RECEIVE_DIRECTION;
|
|
1227
|
-
const ipType = isRemote ? STATS.REMOTE : STATS.LOCAL;
|
|
1228
|
-
|
|
1229
|
-
if (!this.statsResults.candidates) {
|
|
1230
|
-
this.statsResults.candidates = {};
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
this.statsResults.candidates[result.id] = {
|
|
1234
|
-
candidateType: result.candidateType,
|
|
1235
|
-
ipAddress: result.ip, // TODO: add ports
|
|
1236
|
-
relatedAddress: result.relatedAddress,
|
|
1237
|
-
relatedPort: result.relatedPort,
|
|
1238
|
-
relayProtocol: result.relayProtocol,
|
|
1239
|
-
protocol: result.protocol,
|
|
1240
|
-
address: result.address,
|
|
1241
|
-
portNumber: result.port,
|
|
1242
|
-
networkType: result.networkType,
|
|
1243
|
-
priority: result.priority,
|
|
1244
|
-
transport,
|
|
1245
|
-
timestamp: result.time,
|
|
1246
|
-
id: result.id,
|
|
1247
|
-
type: result.type,
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
this.statsResults.connectionType[ipType].candidateType = result.candidateType;
|
|
1251
|
-
this.statsResults.connectionType[ipType].ipAddress = result.ipAddress;
|
|
1252
|
-
|
|
1253
|
-
this.statsResults.connectionType[ipType].networkType =
|
|
1254
|
-
result.networkType === NETWORK_TYPE.VPN ? NETWORK_TYPE.UNKNOWN : result.networkType;
|
|
1255
|
-
this.statsResults.connectionType[ipType].transport = transport;
|
|
1256
|
-
|
|
1257
|
-
this.statsResults[type][sendRecvType].totalRoundTripTime = result.totalRoundTripTime;
|
|
1258
|
-
};
|
|
1259
|
-
|
|
1260
|
-
/**
|
|
1261
|
-
*
|
|
1262
|
-
* @private
|
|
1263
|
-
* @param {*} result
|
|
1264
|
-
* @param {*} type
|
|
1265
|
-
* @returns {void}
|
|
1266
|
-
* @memberof StatsAnalyzer
|
|
1267
|
-
*/
|
|
1268
|
-
compareSentAndReceived(result, type) {
|
|
1269
|
-
// Don't compare on transceivers without a sender.
|
|
1270
|
-
if (!type || !this.statsResults[type].send) {
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
const mediaType = type;
|
|
1275
|
-
|
|
1276
|
-
const currentPacketLoss =
|
|
1277
|
-
result.packetsLost - this.statsResults[mediaType].send.totalPacketsLostOnReceiver;
|
|
1278
|
-
|
|
1279
|
-
this.statsResults[mediaType].send.packetsLostOnReceiver = currentPacketLoss;
|
|
1280
|
-
this.statsResults[mediaType].send.totalPacketsLostOnReceiver = result.packetsLost;
|
|
1281
|
-
|
|
1282
|
-
this.statsResults[mediaType].send.meanRemoteJitter.push(result.jitter);
|
|
1283
|
-
this.statsResults[mediaType].send.meanRoundTripTime.push(result.roundTripTime);
|
|
1284
|
-
|
|
1285
|
-
this.statsResults[mediaType].send.timestamp = result.timestamp;
|
|
1286
|
-
this.statsResults[mediaType].send.ssrc = result.ssrc;
|
|
1287
|
-
this.statsResults[mediaType].send.reportsReceived = result.reportsReceived;
|
|
1288
|
-
|
|
1289
|
-
// Total packloss ratio on this video section of the call
|
|
1290
|
-
this.statsResults[mediaType].send.overAllPacketLossRatio =
|
|
1291
|
-
this.statsResults[mediaType].send.totalPacketsLostOnReceiver > 0
|
|
1292
|
-
? this.statsResults[mediaType].send.totalPacketsLostOnReceiver /
|
|
1293
|
-
this.statsResults[mediaType].send.totalPacketsSent
|
|
1294
|
-
: 0;
|
|
1295
|
-
this.statsResults[mediaType].send.currentPacketLossRatio =
|
|
1296
|
-
this.statsResults[mediaType].send.packetsLostOnReceiver > 0
|
|
1297
|
-
? (this.statsResults[mediaType].send.packetsLostOnReceiver * 100) /
|
|
1298
|
-
(this.statsResults[mediaType].send.packetsSent +
|
|
1299
|
-
this.statsResults[mediaType].send.packetsLostOnReceiver)
|
|
1300
|
-
: 0;
|
|
1301
|
-
|
|
1302
|
-
if (
|
|
1303
|
-
this.statsResults[mediaType].send.maxPacketLossRatio <
|
|
1304
|
-
this.statsResults[mediaType].send.currentPacketLossRatio
|
|
1305
|
-
) {
|
|
1306
|
-
this.statsResults[mediaType].send.maxPacketLossRatio =
|
|
1307
|
-
this.statsResults[mediaType].send.currentPacketLossRatio;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
if (result.type === 'remote-inbound-rtp') {
|
|
1311
|
-
this.networkQualityMonitor.determineUplinkNetworkQuality({
|
|
1312
|
-
mediaType,
|
|
1313
|
-
remoteRtpResults: result,
|
|
1314
|
-
statsAnalyzerCurrentStats: this.statsResults,
|
|
1315
|
-
});
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
}
|