@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.
Files changed (40) hide show
  1. package/lib/ObservedCall.d.ts +4 -19
  2. package/lib/ObservedCall.d.ts.map +1 -1
  3. package/lib/ObservedCall.js +20 -43
  4. package/lib/ObservedCallEventMonitor.d.ts +3 -0
  5. package/lib/ObservedCallEventMonitor.d.ts.map +1 -1
  6. package/lib/ObservedCallEventMonitor.js +7 -0
  7. package/lib/ObservedClient.d.ts +6 -18
  8. package/lib/ObservedClient.d.ts.map +1 -1
  9. package/lib/ObservedClient.js +119 -41
  10. package/lib/ObservedInboundTrack.d.ts +2 -0
  11. package/lib/ObservedInboundTrack.d.ts.map +1 -1
  12. package/lib/ObservedInboundTrack.js +32 -0
  13. package/lib/ObservedOutboundTrack.d.ts +2 -0
  14. package/lib/ObservedOutboundTrack.d.ts.map +1 -1
  15. package/lib/ObservedOutboundTrack.js +5 -0
  16. package/lib/ObservedPeerConnection.d.ts +3 -3
  17. package/lib/ObservedPeerConnection.d.ts.map +1 -1
  18. package/lib/ObservedPeerConnection.js +15 -0
  19. package/lib/Observer.d.ts +2 -5
  20. package/lib/Observer.d.ts.map +1 -1
  21. package/lib/Observer.js +10 -10
  22. package/lib/ObserverEventMonitor.d.ts +3 -0
  23. package/lib/ObserverEventMonitor.d.ts.map +1 -1
  24. package/lib/ObserverEventMonitor.js +7 -0
  25. package/lib/Reports.d.ts +85 -0
  26. package/lib/Reports.d.ts.map +1 -0
  27. package/lib/Reports.js +2 -0
  28. package/lib/index.d.ts +1 -0
  29. package/lib/index.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/ObservedCall.ts +25 -55
  32. package/src/ObservedCallEventMonitor.ts +10 -0
  33. package/src/ObservedClient.ts +141 -46
  34. package/src/ObservedInboundTrack.ts +32 -2
  35. package/src/ObservedOutboundTrack.ts +8 -0
  36. package/src/ObservedPeerConnection.ts +19 -3
  37. package/src/Observer.ts +11 -10
  38. package/src/ObserverEventMonitor.ts +10 -0
  39. package/src/Reports.ts +87 -0
  40. 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
  }
@@ -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.totalInboundPacketsLost += this.deltaInboundPacketsLost;
314
- this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
315
- this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
316
- this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
317
- this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
318
- this.totalSentAudioBytes += this.deltaSentAudioBytes;
319
- this.totalSentVideoBytes += this.deltaSentVideoBytes;
320
- this.totalReceivedBytes += this.deltaTransportReceivedBytes;
321
- this.totalSentBytes += this.deltaTransportSentBytes;
322
- this.totalRttLt50Measurements += this.deltaRttLt50Measurements;
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.totalReceivedVideoBytes * 8) / (elapsedInSeconds);
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
- observedPeerConnection = new ObservedPeerConnection(sample.peerConnectionId, this);
686
-
687
- observedPeerConnection.once('close', () => {
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
- this.observedPeerConnections.set(sample.peerConnectionId, observedPeerConnection);
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', observedPeerConnection);
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?: string;
132
- public iceConnectionState?: string;
133
- public iceGatheringState?: string;
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
- this.totalRttLt50Measurements += call.deltaRttLt50Measurements;
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
  }