@observertc/observer-js 1.0.0-beta.1 → 1.0.0-beta.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/lib/ObservedCall.d.ts +4 -19
- package/lib/ObservedCall.d.ts.map +1 -1
- package/lib/ObservedCall.js +20 -43
- package/lib/ObservedCallEventMonitor.d.ts +3 -0
- package/lib/ObservedCallEventMonitor.d.ts.map +1 -1
- package/lib/ObservedCallEventMonitor.js +7 -0
- package/lib/ObservedClient.d.ts +6 -18
- package/lib/ObservedClient.d.ts.map +1 -1
- package/lib/ObservedClient.js +119 -41
- package/lib/ObservedInboundTrack.d.ts +2 -0
- package/lib/ObservedInboundTrack.d.ts.map +1 -1
- package/lib/ObservedInboundTrack.js +32 -0
- package/lib/ObservedOutboundTrack.d.ts +2 -0
- package/lib/ObservedOutboundTrack.d.ts.map +1 -1
- package/lib/ObservedOutboundTrack.js +5 -0
- package/lib/ObservedPeerConnection.d.ts +3 -3
- package/lib/ObservedPeerConnection.d.ts.map +1 -1
- package/lib/ObservedPeerConnection.js +15 -0
- package/lib/Observer.d.ts +2 -5
- package/lib/Observer.d.ts.map +1 -1
- package/lib/Observer.js +10 -10
- package/lib/ObserverEventMonitor.d.ts +3 -0
- package/lib/ObserverEventMonitor.d.ts.map +1 -1
- package/lib/ObserverEventMonitor.js +7 -0
- package/lib/Reports.d.ts +85 -0
- package/lib/Reports.d.ts.map +1 -0
- package/lib/Reports.js +2 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ObservedCall.ts +25 -55
- package/src/ObservedCallEventMonitor.ts +10 -0
- package/src/ObservedClient.ts +141 -46
- package/src/ObservedInboundTrack.ts +32 -2
- package/src/ObservedOutboundTrack.ts +8 -0
- package/src/ObservedPeerConnection.ts +19 -3
- package/src/Observer.ts +11 -10
- package/src/ObserverEventMonitor.ts +10 -0
- package/src/Reports.ts +87 -0
- package/src/index.ts +4 -0
|
@@ -13,6 +13,7 @@ import { ObservedMediaSource } from './ObservedMediaSource';
|
|
|
13
13
|
import { ObservedOutboundRtp } from './ObservedOutboundRtp';
|
|
14
14
|
import { ObservedOutboundTrack } from './ObservedOutboundTrack';
|
|
15
15
|
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
16
|
+
import { TrackReport } from './Reports';
|
|
16
17
|
import { ClientEvent, ClientIssue, ClientMetaData, ClientSample, ExtensionStat } from './schema/ClientSample';
|
|
17
18
|
|
|
18
19
|
export class ObservedCallEventMonitor<Context> {
|
|
@@ -122,6 +123,8 @@ export class ObservedCallEventMonitor<Context> {
|
|
|
122
123
|
public onMediaSourceAdded?: (mediaSource: ObservedMediaSource, ctx: Context) => void;
|
|
123
124
|
public onMediaSourceRemoved?: (mediaSource: ObservedMediaSource, ctx: Context) => void;
|
|
124
125
|
|
|
126
|
+
public onTrackReport?: (trackReport: TrackReport, ctx: Context) => void;
|
|
127
|
+
|
|
125
128
|
private _onCallAdded(call: ObservedCall) {
|
|
126
129
|
const onCallEmpty = () => this.onCallEmpty?.(call, this.context);
|
|
127
130
|
const onCallNotEmpty = () => this.onCallNotEmpty?.(call, this.context);
|
|
@@ -154,6 +157,7 @@ export class ObservedCallEventMonitor<Context> {
|
|
|
154
157
|
const onUserMediaError = (error: string) => this._onUserMediaError(observedClient, error);
|
|
155
158
|
const onClientUpdated = (...args: ObservedClientEvents['update']) => this.onClientUpdated?.(observedClient, args[0], this.context);
|
|
156
159
|
const onClientEvent = (event: ClientEvent) => this.onClientEvent?.(observedClient, event, this.context);
|
|
160
|
+
const onTrackReport = (trackReport: TrackReport) => this._onTrackReport(observedClient, trackReport);
|
|
157
161
|
|
|
158
162
|
observedClient.once('close', () => {
|
|
159
163
|
observedClient.off('newpeerconnection', this._onPeerConnconnectionAdded);
|
|
@@ -166,6 +170,7 @@ export class ObservedCallEventMonitor<Context> {
|
|
|
166
170
|
observedClient.off('usingturn', onUsingTurn);
|
|
167
171
|
observedClient.off('update', onClientUpdated);
|
|
168
172
|
observedClient.off('clientEvent', onClientEvent);
|
|
173
|
+
observedClient.off('trackreport', onTrackReport);
|
|
169
174
|
|
|
170
175
|
this.onClientClosed?.(observedClient, this.context);
|
|
171
176
|
});
|
|
@@ -181,6 +186,7 @@ export class ObservedCallEventMonitor<Context> {
|
|
|
181
186
|
observedClient.on('extensionStats', onClientExtensionStats);
|
|
182
187
|
observedClient.on('update', onClientUpdated);
|
|
183
188
|
observedClient.on('clientEvent', onClientEvent);
|
|
189
|
+
observedClient.on('trackreport', onTrackReport);
|
|
184
190
|
|
|
185
191
|
this.onClientAdded?.(observedClient, this.context);
|
|
186
192
|
}
|
|
@@ -389,4 +395,8 @@ export class ObservedCallEventMonitor<Context> {
|
|
|
389
395
|
private _onUsingTurn(observedClient: ObservedClient, usingTurn: boolean) {
|
|
390
396
|
this.onClientUsingTurn?.(observedClient, usingTurn, this.context);
|
|
391
397
|
}
|
|
398
|
+
|
|
399
|
+
private _onTrackReport(client: ObservedClient, trackReport: TrackReport) {
|
|
400
|
+
this.onTrackReport?.(trackReport, this.context);
|
|
401
|
+
}
|
|
392
402
|
}
|
package/src/ObservedClient.ts
CHANGED
|
@@ -10,12 +10,16 @@ import { ClientMetaTypes } from './schema/ClientMetaTypes';
|
|
|
10
10
|
import { parseJsonAs } from './common/utils';
|
|
11
11
|
import { CalculatedScore } from './scores/CalculatedScore';
|
|
12
12
|
import { Detectors } from './detectors/Detectors';
|
|
13
|
+
import { ClientReport, TrackReport } from './Reports';
|
|
14
|
+
import { ObservedInboundTrack } from './ObservedInboundTrack';
|
|
15
|
+
import { ObservedOutboundTrack } from './ObservedOutboundTrack';
|
|
13
16
|
|
|
14
17
|
const logger = createLogger('ObservedClient');
|
|
15
18
|
|
|
16
19
|
export type ObservedClientSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = {
|
|
17
20
|
clientId: string;
|
|
18
21
|
appData?: AppData;
|
|
22
|
+
closeClientIfIdleForMs?: number;
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
export type ObservedClientEvents = {
|
|
@@ -32,6 +36,8 @@ export type ObservedClientEvents = {
|
|
|
32
36
|
clientEvent: [ClientEvent];
|
|
33
37
|
|
|
34
38
|
newpeerconnection: [ObservedPeerConnection];
|
|
39
|
+
|
|
40
|
+
trackreport: [TrackReport];
|
|
35
41
|
};
|
|
36
42
|
|
|
37
43
|
export declare interface ObservedClient {
|
|
@@ -51,6 +57,8 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
51
57
|
value: undefined,
|
|
52
58
|
};
|
|
53
59
|
|
|
60
|
+
public readonly settings: Pick<ObservedClientSettings, 'closeClientIfIdleForMs'>;
|
|
61
|
+
|
|
54
62
|
public appData: AppData;
|
|
55
63
|
public attachments?: Record<string, unknown>;
|
|
56
64
|
|
|
@@ -74,20 +82,24 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
74
82
|
public availableOutgoingBitrate = 0;
|
|
75
83
|
public availableIncomingBitrate = 0;
|
|
76
84
|
|
|
77
|
-
public totalInboundPacketsLost = 0;
|
|
78
|
-
public totalInboundPacketsReceived = 0;
|
|
79
|
-
public totalOutboundPacketsSent = 0;
|
|
80
|
-
public totalDataChannelBytesSent = 0;
|
|
81
|
-
public totalDataChannelBytesReceived = 0;
|
|
82
|
-
public totalDataChannelMessagesSent = 0;
|
|
83
|
-
public totalDataChannelMessagesReceived = 0;
|
|
84
|
-
public totalReceivedAudioBytes = 0;
|
|
85
|
-
public totalReceivedVideoBytes = 0;
|
|
86
|
-
public totalSentAudioBytes = 0;
|
|
87
|
-
public totalSentVideoBytes = 0;
|
|
88
|
-
public totalSentBytes = 0;
|
|
89
|
-
public totalReceivedBytes = 0;
|
|
90
|
-
|
|
85
|
+
// public totalInboundPacketsLost = 0;
|
|
86
|
+
// public totalInboundPacketsReceived = 0;
|
|
87
|
+
// public totalOutboundPacketsSent = 0;
|
|
88
|
+
// public totalDataChannelBytesSent = 0;
|
|
89
|
+
// public totalDataChannelBytesReceived = 0;
|
|
90
|
+
// public totalDataChannelMessagesSent = 0;
|
|
91
|
+
// public totalDataChannelMessagesReceived = 0;
|
|
92
|
+
// public totalReceivedAudioBytes = 0;
|
|
93
|
+
// public totalReceivedVideoBytes = 0;
|
|
94
|
+
// public totalSentAudioBytes = 0;
|
|
95
|
+
// public totalSentVideoBytes = 0;
|
|
96
|
+
// public totalSentBytes = 0;
|
|
97
|
+
// public totalReceivedBytes = 0;
|
|
98
|
+
// public totalRttLt50Measurements = 0;
|
|
99
|
+
// public totalRttLt150Measurements = 0;
|
|
100
|
+
// public totalRttLt300Measurements = 0;
|
|
101
|
+
// public totalRttGtOrEq300Measurements = 0;
|
|
102
|
+
|
|
91
103
|
public deltaReceivedAudioBytes = 0;
|
|
92
104
|
public deltaReceivedVideoBytes = 0;
|
|
93
105
|
public deltaSentAudioBytes = 0;
|
|
@@ -121,21 +133,21 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
121
133
|
public numberOfOutboundTracks = 0;
|
|
122
134
|
public numberOfDataChannels = 0;
|
|
123
135
|
|
|
124
|
-
public totalRttLt50Measurements = 0;
|
|
125
|
-
public totalRttLt150Measurements = 0;
|
|
126
|
-
public totalRttLt300Measurements = 0;
|
|
127
|
-
public totalRttGtOrEq300Measurements = 0;
|
|
128
136
|
public deltaNumberOfIssues = 0;
|
|
129
137
|
|
|
130
138
|
public totalScoreSum = 0;
|
|
131
139
|
public numberOfScoreMeasurements = 0;
|
|
132
|
-
public totalNumberOfIssues = 0;
|
|
140
|
+
// public totalNumberOfIssues = 0;
|
|
141
|
+
|
|
142
|
+
public readonly report: ClientReport;
|
|
133
143
|
|
|
134
144
|
public readonly mediaDevices: MetaData.MediaDeviceInfo[] = [];
|
|
135
145
|
public issues: ClientIssue[] = [];
|
|
136
146
|
|
|
137
147
|
private _injections: Pick<ClientSample, 'clientEvents' | 'clientIssues' | 'extensionStats' | 'attachments' | 'clientMetaItems'> = {};
|
|
138
148
|
|
|
149
|
+
private closeTimer?: ReturnType<typeof setTimeout>;
|
|
150
|
+
|
|
139
151
|
public constructor(settings: ObservedClientSettings<AppData>, public readonly call: ObservedCall) {
|
|
140
152
|
super();
|
|
141
153
|
this.setMaxListeners(Infinity);
|
|
@@ -144,6 +156,47 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
144
156
|
this.appData = settings.appData ?? {} as AppData;
|
|
145
157
|
|
|
146
158
|
this.detectors = new Detectors();
|
|
159
|
+
this.settings = {
|
|
160
|
+
closeClientIfIdleForMs: settings.closeClientIfIdleForMs,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
this.report = {
|
|
164
|
+
callId: this.call.callId,
|
|
165
|
+
clientId: this.clientId,
|
|
166
|
+
rttDistribution: {
|
|
167
|
+
gtOrEq300ms: 0,
|
|
168
|
+
lt50ms: 0,
|
|
169
|
+
lt150ms: 0,
|
|
170
|
+
lt300ms: 0,
|
|
171
|
+
count: 0,
|
|
172
|
+
sum: 0,
|
|
173
|
+
},
|
|
174
|
+
scoreDistribution: {
|
|
175
|
+
'0': 0,
|
|
176
|
+
'1': 0,
|
|
177
|
+
'2': 0,
|
|
178
|
+
'3': 0,
|
|
179
|
+
'4': 0,
|
|
180
|
+
'5': 0,
|
|
181
|
+
count: 0,
|
|
182
|
+
sum: 0,
|
|
183
|
+
},
|
|
184
|
+
issues: {},
|
|
185
|
+
totalAudioBytesReceived: 0,
|
|
186
|
+
totalVideoBytesReceived: 0,
|
|
187
|
+
totalAudioBytesSent: 0,
|
|
188
|
+
totalVideoBytesSent: 0,
|
|
189
|
+
totalDataChannelBytesReceived: 0,
|
|
190
|
+
totalDataChannelBytesSent: 0,
|
|
191
|
+
totalDataChannelMessagesReceived: 0,
|
|
192
|
+
totalDataChannelMessagesSent: 0,
|
|
193
|
+
totalInboundRtpBytesReceived: 0,
|
|
194
|
+
totalInboundRtpPacketsLost: 0,
|
|
195
|
+
totalInboundRtpPacketsReceived: 0,
|
|
196
|
+
totalOutboundRtpBytesSent: 0,
|
|
197
|
+
totalOutboundRtpPacketsSent: 0,
|
|
198
|
+
totalNumberOfIssues: 0,
|
|
199
|
+
};
|
|
147
200
|
}
|
|
148
201
|
|
|
149
202
|
public get numberOfPeerConnections() {
|
|
@@ -179,6 +232,11 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
179
232
|
public accept(sample: ClientSample): void {
|
|
180
233
|
if (this.closed) throw new Error(`Client ${this.clientId} is closed`);
|
|
181
234
|
|
|
235
|
+
if (this.closeTimer) {
|
|
236
|
+
clearTimeout(this.closeTimer);
|
|
237
|
+
this.closeTimer = undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
182
240
|
const now = Date.now();
|
|
183
241
|
const elapsedInMs = now - this.updated;
|
|
184
242
|
const elapsedInSeconds = elapsedInMs / 1000;
|
|
@@ -286,13 +344,19 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
286
344
|
}
|
|
287
345
|
if (observedPeerConnection.currentRttInMs < 50) {
|
|
288
346
|
this.deltaRttLt50Measurements += 1;
|
|
347
|
+
this.report.rttDistribution.lt50ms += 1;
|
|
289
348
|
} else if (observedPeerConnection.currentRttInMs < 150) {
|
|
290
349
|
this.deltaRttLt150Measurements += 1;
|
|
350
|
+
this.report.rttDistribution.lt150ms += 1;
|
|
291
351
|
} else if (observedPeerConnection.currentRttInMs < 300) {
|
|
292
352
|
this.deltaRttLt300Measurements += 1;
|
|
353
|
+
this.report.rttDistribution.lt300ms += 1;
|
|
293
354
|
} else if (300 <= observedPeerConnection.currentRttInMs) {
|
|
294
355
|
this.deltaRttGtOrEq300Measurements += 1;
|
|
356
|
+
this.report.rttDistribution.gtOrEq300ms += 1;
|
|
295
357
|
}
|
|
358
|
+
this.report.rttDistribution.count += 1;
|
|
359
|
+
this.report.rttDistribution.sum += observedPeerConnection.currentRttInMs;
|
|
296
360
|
|
|
297
361
|
sumOfRtts += observedPeerConnection.currentRttInMs;
|
|
298
362
|
++numberOfRttMeasurements;
|
|
@@ -306,32 +370,37 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
306
370
|
// emit new attachments?
|
|
307
371
|
this.attachments = sample.attachments;
|
|
308
372
|
|
|
309
|
-
this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
|
|
310
|
-
this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
|
|
311
|
-
this.totalDataChannelMessagesReceived += this.deltaDataChannelMessagesReceived;
|
|
312
|
-
this.totalDataChannelMessagesSent += this.deltaDataChannelMessagesSent;
|
|
313
|
-
this.
|
|
314
|
-
this.
|
|
315
|
-
this.
|
|
316
|
-
this.
|
|
317
|
-
this.
|
|
318
|
-
this.
|
|
319
|
-
this.
|
|
320
|
-
this.
|
|
321
|
-
this.
|
|
322
|
-
this.
|
|
323
|
-
this.totalRttLt150Measurements += this.deltaRttLt150Measurements;
|
|
324
|
-
this.totalRttLt300Measurements += this.deltaRttLt300Measurements;
|
|
325
|
-
this.totalRttGtOrEq300Measurements += this.deltaRttGtOrEq300Measurements;
|
|
326
|
-
this.totalNumberOfIssues += this.deltaNumberOfIssues;
|
|
373
|
+
this.report.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
|
|
374
|
+
this.report.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
|
|
375
|
+
this.report.totalDataChannelMessagesReceived += this.deltaDataChannelMessagesReceived;
|
|
376
|
+
this.report.totalDataChannelMessagesSent += this.deltaDataChannelMessagesSent;
|
|
377
|
+
this.report.totalInboundRtpPacketsLost += this.deltaInboundPacketsLost;
|
|
378
|
+
this.report.totalInboundRtpPacketsReceived += this.deltaInboundPacketsReceived;
|
|
379
|
+
this.report.totalOutboundRtpPacketsSent += this.deltaOutboundPacketsSent;
|
|
380
|
+
this.report.totalAudioBytesReceived += this.deltaReceivedAudioBytes;
|
|
381
|
+
this.report.totalVideoBytesReceived += this.deltaReceivedVideoBytes;
|
|
382
|
+
this.report.totalAudioBytesSent += this.deltaSentAudioBytes;
|
|
383
|
+
this.report.totalVideoBytesSent += this.deltaSentVideoBytes;
|
|
384
|
+
this.report.totalInboundRtpBytesReceived += this.deltaTransportReceivedBytes;
|
|
385
|
+
this.report.totalOutboundRtpBytesSent += this.deltaTransportSentBytes;
|
|
386
|
+
this.report.totalNumberOfIssues += this.deltaNumberOfIssues;
|
|
327
387
|
|
|
328
388
|
this.receivingAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (elapsedInSeconds);
|
|
329
|
-
this.receivingVideoBitrate = (this.
|
|
389
|
+
this.receivingVideoBitrate = (this.deltaReceivedVideoBytes * 8) / (elapsedInSeconds);
|
|
330
390
|
this.sendingAudioBitrate = (this.deltaSentAudioBytes * 8) / (elapsedInSeconds);
|
|
331
391
|
this.sendingVideoBitrate = (this.deltaSentVideoBytes * 8) / (elapsedInSeconds);
|
|
332
392
|
this.currentAvgRttInMs = 0 < numberOfRttMeasurements ? sumOfRtts / numberOfRttMeasurements : undefined;
|
|
333
393
|
|
|
334
394
|
this.calculatedScore.value = sample.score;
|
|
395
|
+
|
|
396
|
+
if (sample.score !== undefined) {
|
|
397
|
+
const bucket = Math.floor(sample.score).toString() as '0' | '1' | '2' | '3' | '4' | '5';
|
|
398
|
+
|
|
399
|
+
this.report.scoreDistribution[bucket] = (this.report.scoreDistribution[bucket] ?? 0) + 1;
|
|
400
|
+
this.report.scoreDistribution.count += 1;
|
|
401
|
+
this.report.scoreDistribution.sum += sample.score;
|
|
402
|
+
}
|
|
403
|
+
|
|
335
404
|
this.detectors.update();
|
|
336
405
|
|
|
337
406
|
this.lastSampleTimestamp = sample.timestamp;
|
|
@@ -347,6 +416,12 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
347
416
|
this.totalScoreSum += this.calculatedScore.value;
|
|
348
417
|
++this.numberOfScoreMeasurements;
|
|
349
418
|
}
|
|
419
|
+
|
|
420
|
+
if (this.settings.closeClientIfIdleForMs !== undefined) {
|
|
421
|
+
this.closeTimer = setTimeout(() => {
|
|
422
|
+
this.close();
|
|
423
|
+
}, this.settings.closeClientIfIdleForMs);
|
|
424
|
+
}
|
|
350
425
|
}
|
|
351
426
|
|
|
352
427
|
private _processClientEvent(event: ClientEvent, postBuffer?: ClientEvent[]) {
|
|
@@ -538,7 +613,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
538
613
|
const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
|
|
539
614
|
|
|
540
615
|
if (observedPeerConnection) {
|
|
541
|
-
observedPeerConnection.iceConnectionState = payload.iceConnectionState;
|
|
616
|
+
observedPeerConnection.iceConnectionState = payload.iceConnectionState as 'new' | 'checking' | 'connected' | 'completed' | 'failed' | 'disconnected' | 'closed';
|
|
542
617
|
observedPeerConnection.emit('iceconnectionstatechange', {
|
|
543
618
|
state: payload.iceConnectionState,
|
|
544
619
|
});
|
|
@@ -557,7 +632,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
557
632
|
const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
|
|
558
633
|
|
|
559
634
|
if (observedPeerConnection) {
|
|
560
|
-
observedPeerConnection.iceGatheringState = payload.iceGatheringState;
|
|
635
|
+
observedPeerConnection.iceGatheringState = payload.iceGatheringState as 'new' | 'gathering' | 'complete';
|
|
561
636
|
observedPeerConnection.emit('icegatheringstatechange', {
|
|
562
637
|
state: payload.iceGatheringState,
|
|
563
638
|
});
|
|
@@ -576,7 +651,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
576
651
|
const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
|
|
577
652
|
|
|
578
653
|
if (observedPeerConnection) {
|
|
579
|
-
observedPeerConnection.connectionState = payload.peerConnectionState;
|
|
654
|
+
observedPeerConnection.connectionState = payload.peerConnectionState as 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';
|
|
580
655
|
observedPeerConnection.emit('connectionstatechange', {
|
|
581
656
|
state: payload.peerConnectionState,
|
|
582
657
|
});
|
|
@@ -662,6 +737,8 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
662
737
|
public addIssue(issue: ClientIssue) {
|
|
663
738
|
if (this.closed) return;
|
|
664
739
|
|
|
740
|
+
this.report.issues[issue.type] = (this.report.issues[issue.type] ?? 0) + 1;
|
|
741
|
+
|
|
665
742
|
this.emit('issue', issue);
|
|
666
743
|
this.call.observer.emit('client-issue', this, issue);
|
|
667
744
|
}
|
|
@@ -682,14 +759,32 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
682
759
|
), void 0);
|
|
683
760
|
}
|
|
684
761
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
762
|
+
const newObservedPeerConnection = new ObservedPeerConnection(sample.peerConnectionId, this);
|
|
763
|
+
const onInboundTrackRemoved = (track: ObservedInboundTrack) => {
|
|
764
|
+
this.emit('trackreport', {
|
|
765
|
+
direction: 'inbound',
|
|
766
|
+
...track.report,
|
|
767
|
+
});
|
|
768
|
+
};
|
|
769
|
+
const onOutboundTrackRemoved = (track: ObservedOutboundTrack) => {
|
|
770
|
+
this.emit('trackreport', {
|
|
771
|
+
direction: 'outbound',
|
|
772
|
+
...track.report,
|
|
773
|
+
});
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
newObservedPeerConnection.once('close', () => {
|
|
777
|
+
newObservedPeerConnection?.off('removed-inbound-track', onInboundTrackRemoved);
|
|
778
|
+
newObservedPeerConnection?.off('removed-outbound-track', onOutboundTrackRemoved);
|
|
688
779
|
this.observedPeerConnections.delete(sample.peerConnectionId);
|
|
689
780
|
});
|
|
690
|
-
|
|
781
|
+
newObservedPeerConnection.on('removed-inbound-track', onInboundTrackRemoved);
|
|
782
|
+
newObservedPeerConnection.on('removed-outbound-track', onOutboundTrackRemoved);
|
|
783
|
+
this.observedPeerConnections.set(sample.peerConnectionId, newObservedPeerConnection);
|
|
691
784
|
|
|
692
|
-
this.emit('newpeerconnection',
|
|
785
|
+
this.emit('newpeerconnection', newObservedPeerConnection);
|
|
786
|
+
|
|
787
|
+
observedPeerConnection = newObservedPeerConnection;
|
|
693
788
|
}
|
|
694
789
|
|
|
695
790
|
observedPeerConnection.accept(sample);
|
|
@@ -5,6 +5,7 @@ import { Detectors } from './detectors/Detectors';
|
|
|
5
5
|
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
6
6
|
import { ObservedInboundRtp } from './ObservedInboundRtp';
|
|
7
7
|
import { ObservedMediaPlayout } from './ObservedMediaPlayout';
|
|
8
|
+
import { InboundTrackReport } from './Reports';
|
|
8
9
|
|
|
9
10
|
export class ObservedInboundTrack implements InboundTrackSample {
|
|
10
11
|
public readonly detectors: Detectors;
|
|
@@ -13,6 +14,7 @@ export class ObservedInboundTrack implements InboundTrackSample {
|
|
|
13
14
|
value: undefined,
|
|
14
15
|
};
|
|
15
16
|
public appData?: Record<string, unknown>;
|
|
17
|
+
public report: InboundTrackReport;
|
|
16
18
|
|
|
17
19
|
private _visited = false;
|
|
18
20
|
|
|
@@ -32,6 +34,21 @@ export class ObservedInboundTrack implements InboundTrackSample {
|
|
|
32
34
|
private readonly _mediaPlayout?: ObservedMediaPlayout,
|
|
33
35
|
) {
|
|
34
36
|
this.detectors = new Detectors();
|
|
37
|
+
|
|
38
|
+
this.report = {
|
|
39
|
+
trackId: this.id,
|
|
40
|
+
kind: this.kind,
|
|
41
|
+
fractionLostDistribution: {
|
|
42
|
+
gtOrEq050: 0,
|
|
43
|
+
lt001: 0,
|
|
44
|
+
lt005: 0,
|
|
45
|
+
lt010: 0,
|
|
46
|
+
lt020: 0,
|
|
47
|
+
lt050: 0,
|
|
48
|
+
count: 0,
|
|
49
|
+
sum: 0,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
public get score() {
|
|
@@ -68,8 +85,21 @@ export class ObservedInboundTrack implements InboundTrackSample {
|
|
|
68
85
|
this.timestamp = stats.timestamp;
|
|
69
86
|
this.calculatedScore.value = stats.score;
|
|
70
87
|
this.attachments = stats.attachments;
|
|
71
|
-
|
|
88
|
+
|
|
89
|
+
const fl = this._inboundRtp?.fractionLost;
|
|
90
|
+
|
|
91
|
+
if (fl !== undefined) {
|
|
92
|
+
if (fl < 0.01) this.report.fractionLostDistribution.lt001 += 1;
|
|
93
|
+
else if (fl < 0.05) this.report.fractionLostDistribution.lt005 += 1;
|
|
94
|
+
else if (fl < 0.1) this.report.fractionLostDistribution.lt010 += 1;
|
|
95
|
+
else if (fl < 0.2) this.report.fractionLostDistribution.lt020 += 1;
|
|
96
|
+
else if (fl < 0.5) this.report.fractionLostDistribution.lt050 += 1;
|
|
97
|
+
else this.report.fractionLostDistribution.gtOrEq050 += 1;
|
|
98
|
+
|
|
99
|
+
this.report.fractionLostDistribution.count += 1;
|
|
100
|
+
this.report.fractionLostDistribution.sum += fl;
|
|
101
|
+
}
|
|
102
|
+
|
|
72
103
|
this.detectors.update();
|
|
73
104
|
}
|
|
74
|
-
|
|
75
105
|
}
|
|
@@ -5,11 +5,14 @@ import { Detectors } from './detectors/Detectors';
|
|
|
5
5
|
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
6
6
|
import { ObservedOutboundRtp } from './ObservedOutboundRtp';
|
|
7
7
|
import { ObservedMediaSource } from './ObservedMediaSource';
|
|
8
|
+
import { OutboundTrackReport } from './Reports';
|
|
8
9
|
|
|
9
10
|
export class ObservedOutboundTrack implements OutboundTrackSample {
|
|
10
11
|
public readonly detectors: Detectors;
|
|
11
12
|
private _visited = false;
|
|
12
13
|
public appData?: Record<string, unknown>;
|
|
14
|
+
public readonly report: OutboundTrackReport;
|
|
15
|
+
|
|
13
16
|
public readonly calculatedScore: CalculatedScore = {
|
|
14
17
|
weight: 1,
|
|
15
18
|
value: undefined,
|
|
@@ -30,6 +33,11 @@ export class ObservedOutboundTrack implements OutboundTrackSample {
|
|
|
30
33
|
private readonly _outboundRtps?: ObservedOutboundRtp[],
|
|
31
34
|
private readonly _mediaSource?: ObservedMediaSource,
|
|
32
35
|
) {
|
|
36
|
+
this.report = {
|
|
37
|
+
trackId: this.id,
|
|
38
|
+
kind: this.kind as 'audio' | 'video',
|
|
39
|
+
};
|
|
40
|
+
|
|
33
41
|
this.detectors = new Detectors();
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -128,9 +128,9 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
128
128
|
public closedAt?: number;
|
|
129
129
|
public updated = Date.now();
|
|
130
130
|
|
|
131
|
-
public connectionState?:
|
|
132
|
-
public iceConnectionState?:
|
|
133
|
-
public iceGatheringState?:
|
|
131
|
+
public connectionState?: 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';
|
|
132
|
+
public iceConnectionState?: 'new' | 'checking' | 'connected' | 'completed' | 'failed' | 'disconnected' | 'closed';
|
|
133
|
+
public iceGatheringState?: 'new' | 'gathering' | 'complete';
|
|
134
134
|
|
|
135
135
|
public availableIncomingBitrate = 0;
|
|
136
136
|
public availableOutgoingBitrate = 0;
|
|
@@ -286,6 +286,22 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
286
286
|
if (this.closed) return;
|
|
287
287
|
this.closed = true;
|
|
288
288
|
|
|
289
|
+
this.observedCertificates.forEach((cert) => this.emit('removed-certificate', cert));
|
|
290
|
+
this.observedCodecs.forEach((codec) => this.emit('removed-codec', codec));
|
|
291
|
+
this.observedDataChannels.forEach((dc) => this.emit('removed-data-channel', dc));
|
|
292
|
+
this.observedIceCandidates.forEach((candidate) => this.emit('removed-ice-candidate', candidate));
|
|
293
|
+
this.observedIceCandidatesPair.forEach((pair) => this.emit('removed-ice-candidate-pair', pair));
|
|
294
|
+
this.observedIceTransports.forEach((transport) => this.emit('removed-ice-transport', transport));
|
|
295
|
+
this.observedInboundRtps.forEach((rtp) => this.emit('removed-inbound-rtp', rtp));
|
|
296
|
+
this.observedInboundTracks.forEach((track) => this.emit('removed-inbound-track', track));
|
|
297
|
+
this.observedMediaPlayouts.forEach((playout) => this.emit('removed-media-playout', playout));
|
|
298
|
+
this.observedMediaSources.forEach((source) => this.emit('removed-media-source', source));
|
|
299
|
+
this.observedOutboundRtps.forEach((rtp) => this.emit('removed-outbound-rtp', rtp));
|
|
300
|
+
this.observedOutboundTracks.forEach((track) => this.emit('removed-outbound-track', track));
|
|
301
|
+
this.observedPeerConnectionTransports.forEach((transport) => this.emit('removed-peer-connection-transport', transport));
|
|
302
|
+
this.observedRemoteInboundRtps.forEach((rtp) => this.emit('removed-remote-inbound-rtp', rtp));
|
|
303
|
+
this.observedRemoteOutboundRtps.forEach((rtp) => this.emit('removed-remote-outbound-rtp', rtp));
|
|
304
|
+
|
|
289
305
|
this.observedCertificates.clear();
|
|
290
306
|
this.observedCodecs.clear();
|
|
291
307
|
this.observedDataChannels.clear();
|
package/src/Observer.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type ObserverConfig<AppData extends Record<string, unknown> = Record<stri
|
|
|
31
31
|
defaultCallUpdatePolicy?: ObservedCallSettings['updatePolicy'],
|
|
32
32
|
defaultCallUpdateIntervalInMs?: number,
|
|
33
33
|
appData?: AppData,
|
|
34
|
+
closeClientIfIdleForMs?: number,
|
|
35
|
+
closeCallIfEmptyForMs?: number,
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export declare interface Observer {
|
|
@@ -51,11 +53,6 @@ export class Observer<AppData extends Record<string, unknown> = Record<string, u
|
|
|
51
53
|
|
|
52
54
|
public totalAddedCall = 0;
|
|
53
55
|
public totalRemovedCall = 0;
|
|
54
|
-
public totalRttLt50Measurements = 0;
|
|
55
|
-
public totalRttLt150Measurements = 0;
|
|
56
|
-
public totalRttLt300Measurements = 0;
|
|
57
|
-
public totalRttGtOrEq300Measurements = 0;
|
|
58
|
-
public totalClientIssues = 0;
|
|
59
56
|
|
|
60
57
|
public numberOfClientsUsingTurn = 0;
|
|
61
58
|
public numberOfClients = 0;
|
|
@@ -125,6 +122,10 @@ export class Observer<AppData extends Record<string, unknown> = Record<string, u
|
|
|
125
122
|
settings.updatePolicy = this.config.defaultCallUpdatePolicy;
|
|
126
123
|
settings.updateIntervalInMs = this.config.defaultCallUpdateIntervalInMs;
|
|
127
124
|
}
|
|
125
|
+
if (!settings.closeCallIfEmptyForMs) {
|
|
126
|
+
settings.closeCallIfEmptyForMs = this.config.closeCallIfEmptyForMs;
|
|
127
|
+
}
|
|
128
|
+
|
|
128
129
|
const observedCall = new ObservedCall(settings, this);
|
|
129
130
|
const onCallUpdated = () => this._onObservedCallUpdated(observedCall);
|
|
130
131
|
|
|
@@ -132,6 +133,10 @@ export class Observer<AppData extends Record<string, unknown> = Record<string, u
|
|
|
132
133
|
|
|
133
134
|
if (settings.remoteTrackResolvePolicy === 'mediasoup-sfu') {
|
|
134
135
|
observedCall.remoteTrackResolver = new MediasoupRemoteTrackResolver(observedCall);
|
|
136
|
+
} else if (settings.remoteTrackResolvePolicy === 'p2p') {
|
|
137
|
+
// For future implementation
|
|
138
|
+
} else if (settings.remoteTrackResolvePolicy === 'none' || !settings.remoteTrackResolvePolicy) {
|
|
139
|
+
// do nothing
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
observedCall.once('close', () => {
|
|
@@ -207,11 +212,7 @@ export class Observer<AppData extends Record<string, unknown> = Record<string, u
|
|
|
207
212
|
}
|
|
208
213
|
|
|
209
214
|
private _onObservedCallUpdated(call: ObservedCall) {
|
|
210
|
-
|
|
211
|
-
this.totalRttLt150Measurements += call.deltaRttLt150Measurements;
|
|
212
|
-
this.totalRttLt300Measurements += call.deltaRttLt300Measurements;
|
|
213
|
-
this.totalRttGtOrEq300Measurements += call.deltaRttGtOrEq300Measurements;
|
|
214
|
-
this.totalClientIssues += call.deltaNumberOfIssues;
|
|
215
|
+
call;
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
public createEventMonitor<CTX = unknown>(ctx?: CTX): ObserverEventMonitor<CTX> {
|
|
@@ -14,6 +14,7 @@ import { ObservedOutboundRtp } from './ObservedOutboundRtp';
|
|
|
14
14
|
import { ObservedOutboundTrack } from './ObservedOutboundTrack';
|
|
15
15
|
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
16
16
|
import { Observer } from './Observer';
|
|
17
|
+
import { TrackReport } from './Reports';
|
|
17
18
|
import { ClientEvent, ClientIssue, ClientMetaData, ClientSample, ExtensionStat } from './schema/ClientSample';
|
|
18
19
|
|
|
19
20
|
export class ObserverEventMonitor<Context> {
|
|
@@ -160,6 +161,8 @@ export class ObserverEventMonitor<Context> {
|
|
|
160
161
|
public onMediaSourceRemoved?: (mediaSource: ObservedMediaSource, ctx: Context) => void;
|
|
161
162
|
public onMediaSourceUpdated?: (mediaSource: ObservedMediaSource, ctx: Context) => void;
|
|
162
163
|
|
|
164
|
+
public onTrackReport?: (report: TrackReport, observedClient: ObservedClient, ctx: Context) => void;
|
|
165
|
+
|
|
163
166
|
private _onCallAdded(call: ObservedCall) {
|
|
164
167
|
const onCallEmpty = () => this.onCallEmpty?.(call, this.context);
|
|
165
168
|
const onCallNotEmpty = () => this.onCallNotEmpty?.(call, this.context);
|
|
@@ -192,6 +195,7 @@ export class ObserverEventMonitor<Context> {
|
|
|
192
195
|
const onUserMediaError = (error: string) => this._onUserMediaError(observedClient, error);
|
|
193
196
|
const onClientUpdated = (...args: ObservedClientEvents['update']) => this.onClientUpdated?.(observedClient, args[0], this.context);
|
|
194
197
|
const onClientEvent = (event: ClientEvent) => this.onClientEvent?.(observedClient, event, this.context);
|
|
198
|
+
const onTrackReport = (report: TrackReport) => this._onTrackReport(report, observedClient);
|
|
195
199
|
|
|
196
200
|
observedClient.once('close', () => {
|
|
197
201
|
observedClient.off('newpeerconnection', this._onPeerConnconnectionAdded);
|
|
@@ -205,6 +209,7 @@ export class ObserverEventMonitor<Context> {
|
|
|
205
209
|
observedClient.off('extensionStats', onClientExtensionStats);
|
|
206
210
|
observedClient.off('update', onClientUpdated);
|
|
207
211
|
observedClient.off('clientEvent', onClientEvent);
|
|
212
|
+
observedClient.off('trackreport', onTrackReport);
|
|
208
213
|
|
|
209
214
|
this.onClientClosed?.(observedClient, this.context);
|
|
210
215
|
});
|
|
@@ -220,6 +225,7 @@ export class ObserverEventMonitor<Context> {
|
|
|
220
225
|
observedClient.on('extensionStats', onClientExtensionStats);
|
|
221
226
|
observedClient.on('update', onClientUpdated);
|
|
222
227
|
observedClient.on('clientEvent', onClientEvent);
|
|
228
|
+
observedClient.on('trackreport', onTrackReport);
|
|
223
229
|
|
|
224
230
|
this.onClientAdded?.(observedClient, this.context);
|
|
225
231
|
}
|
|
@@ -548,4 +554,8 @@ export class ObserverEventMonitor<Context> {
|
|
|
548
554
|
private _onUsingTurn(observedClient: ObservedClient, usingTurn: boolean) {
|
|
549
555
|
this.onClientUsingTurn?.(observedClient, usingTurn, this.context);
|
|
550
556
|
}
|
|
557
|
+
|
|
558
|
+
private _onTrackReport(report: TrackReport, observedClient: ObservedClient) {
|
|
559
|
+
this.onTrackReport?.(report, observedClient, this.context);
|
|
560
|
+
}
|
|
551
561
|
}
|