@observertc/observer-js 0.40.7-beta → 0.40.9-beta

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 (62) hide show
  1. package/lib/ObservedClient.d.ts +5 -1
  2. package/lib/ObservedClient.d.ts.map +1 -1
  3. package/lib/ObservedClient.js +8 -5
  4. package/lib/ObservedDataChannel.d.ts +5 -1
  5. package/lib/ObservedDataChannel.d.ts.map +1 -1
  6. package/lib/ObservedDataChannel.js +3 -1
  7. package/lib/ObservedICE.d.ts +17 -1
  8. package/lib/ObservedICE.d.ts.map +1 -1
  9. package/lib/ObservedICE.js +37 -2
  10. package/lib/ObservedInboundTrack.d.ts +5 -1
  11. package/lib/ObservedInboundTrack.d.ts.map +1 -1
  12. package/lib/ObservedInboundTrack.js +3 -1
  13. package/lib/ObservedOutboundTrack.d.ts +5 -1
  14. package/lib/ObservedOutboundTrack.d.ts.map +1 -1
  15. package/lib/ObservedOutboundTrack.js +3 -1
  16. package/lib/ObservedPeerConnection.d.ts +5 -1
  17. package/lib/ObservedPeerConnection.d.ts.map +1 -1
  18. package/lib/ObservedPeerConnection.js +8 -4
  19. package/lib/ObservedSfu.d.ts +5 -1
  20. package/lib/ObservedSfu.d.ts.map +1 -1
  21. package/lib/ObservedSfu.js +6 -2
  22. package/lib/ObservedSfuInboundRtpPad.d.ts +5 -1
  23. package/lib/ObservedSfuInboundRtpPad.d.ts.map +1 -1
  24. package/lib/ObservedSfuInboundRtpPad.js +6 -2
  25. package/lib/ObservedSfuOutboundRtpPad.d.ts +5 -1
  26. package/lib/ObservedSfuOutboundRtpPad.d.ts.map +1 -1
  27. package/lib/ObservedSfuOutboundRtpPad.js +6 -2
  28. package/lib/ObservedSfuSctpChannel.d.ts +5 -1
  29. package/lib/ObservedSfuSctpChannel.d.ts.map +1 -1
  30. package/lib/ObservedSfuSctpChannel.js +6 -2
  31. package/lib/ObservedSfuTransport.d.ts +5 -1
  32. package/lib/ObservedSfuTransport.d.ts.map +1 -1
  33. package/lib/ObservedSfuTransport.js +6 -2
  34. package/lib/Observer.d.ts +7 -0
  35. package/lib/Observer.d.ts.map +1 -1
  36. package/lib/Observer.js +70 -0
  37. package/lib/{CallSummary.d.ts → monitors/CallSummary.d.ts} +7 -1
  38. package/lib/monitors/CallSummary.d.ts.map +1 -0
  39. package/lib/monitors/CallSummaryMonitor.d.ts +25 -0
  40. package/lib/monitors/CallSummaryMonitor.d.ts.map +1 -0
  41. package/lib/monitors/CallSummaryMonitor.js +82 -0
  42. package/lib/monitors/TurnUsageMonitor.d.ts +32 -0
  43. package/lib/monitors/TurnUsageMonitor.d.ts.map +1 -0
  44. package/lib/monitors/TurnUsageMonitor.js +75 -0
  45. package/package.json +4 -7
  46. package/src/ObservedClient.ts +13 -6
  47. package/src/ObservedDataChannel.ts +6 -2
  48. package/src/ObservedICE.ts +53 -4
  49. package/src/ObservedInboundTrack.ts +6 -2
  50. package/src/ObservedOutboundTrack.ts +6 -2
  51. package/src/ObservedPeerConnection.ts +13 -6
  52. package/src/ObservedSfu.ts +10 -3
  53. package/src/ObservedSfuInboundRtpPad.ts +9 -3
  54. package/src/ObservedSfuOutboundRtpPad.ts +9 -3
  55. package/src/ObservedSfuSctpChannel.ts +9 -3
  56. package/src/ObservedSfuTransport.ts +10 -4
  57. package/src/Observer.ts +86 -0
  58. package/src/{CallSummary.ts → monitors/CallSummary.ts} +8 -2
  59. package/src/monitors/CallSummaryMonitor.ts +107 -0
  60. package/src/monitors/TurnUsageMonitor.ts +108 -0
  61. package/lib/CallSummary.d.ts.map +0 -1
  62. /package/lib/{CallSummary.js → monitors/CallSummary.js} +0 -0
@@ -67,11 +67,15 @@ class ObservedSfuTransport extends events_1.EventEmitter {
67
67
  this.emit('close');
68
68
  }
69
69
  update(sample, timestamp) {
70
+ const now = Date.now();
71
+ const elapsedTimeInMs = now - this._updated;
70
72
  const report = Object.assign(Object.assign({ serviceId: this.serviceId, mediaUnitId: this.mediaUnitId, sfuId: this.sfuId, callId: undefined, roomId: undefined }, sample), { timestamp, marker: this.marker });
71
73
  this.reports.addSfuTransportReport(report);
72
74
  this.stats = sample;
73
- this._updated = Date.now();
74
- this.emit('update');
75
+ this._updated = now;
76
+ this.emit('update', {
77
+ elapsedTimeInMs,
78
+ });
75
79
  }
76
80
  createSfuOutboundRtpPad(model) {
77
81
  if (this._closed)
package/lib/Observer.d.ts CHANGED
@@ -5,6 +5,8 @@ import { EventEmitter } from 'events';
5
5
  import { PartialBy } from './common/utils';
6
6
  import { ObserverSinkContext } from './common/types';
7
7
  import { ObservedSfu, ObservedSfuModel } from './ObservedSfu';
8
+ import { CallSummaryMonitor } from './monitors/CallSummaryMonitor';
9
+ import { TurnUsageMonitor } from './monitors/TurnUsageMonitor';
8
10
  export type ObserverEvents = {
9
11
  'newcall': [ObservedCall];
10
12
  'newsfu': [ObservedSfu];
@@ -42,6 +44,7 @@ export declare class Observer extends EventEmitter {
42
44
  readonly reports: ReportsCollector;
43
45
  private readonly _observedCalls;
44
46
  private readonly _observedSfus;
47
+ private readonly _monitors;
45
48
  private _reportTimer?;
46
49
  private _closed;
47
50
  constructor(config: ObserverConfig);
@@ -50,6 +53,10 @@ export declare class Observer extends EventEmitter {
50
53
  started?: number;
51
54
  }): ObservedCall<T>;
52
55
  createObservedSfu<AppData extends Record<string, unknown> = Record<string, unknown>>(model: ObservedSfuModel, appData: AppData): ObservedSfu<AppData>;
56
+ createCallSummaryMonitor(options?: {
57
+ timeoutAfterCallClose?: number;
58
+ }): CallSummaryMonitor;
59
+ createTurnUsageMonitor(): TurnUsageMonitor;
53
60
  get observedCalls(): ReadonlyMap<string, ObservedCall>;
54
61
  get observedSfus(): ReadonlyMap<string, ObservedSfu>;
55
62
  get closed(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Observer.d.ts","sourceRoot":"","sources":["../src/Observer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Observer.d.ts","sourceRoot":"","sources":["../src/Observer.ts"],"names":[],"mappings":"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"}
package/lib/Observer.js CHANGED
@@ -18,6 +18,8 @@ const ReportsCollector_1 = require("./ReportsCollector");
18
18
  const events_1 = require("events");
19
19
  const callEventReports_1 = require("./common/callEventReports");
20
20
  const ObservedSfu_1 = require("./ObservedSfu");
21
+ const CallSummaryMonitor_1 = require("./monitors/CallSummaryMonitor");
22
+ const TurnUsageMonitor_1 = require("./monitors/TurnUsageMonitor");
21
23
  const logger = (0, logger_1.createLogger)('Observer');
22
24
  class Observer extends events_1.EventEmitter {
23
25
  static create(providedConfig) {
@@ -39,6 +41,7 @@ class Observer extends events_1.EventEmitter {
39
41
  this.reports = new ReportsCollector_1.ReportsCollector();
40
42
  this._observedCalls = new Map();
41
43
  this._observedSfus = new Map();
44
+ this._monitors = new Map();
42
45
  this._closed = false;
43
46
  this.setMaxListeners(Infinity);
44
47
  logger.debug('Observer is created with config', this.config);
@@ -93,6 +96,73 @@ class Observer extends events_1.EventEmitter {
93
96
  this.emit('newsfu', sfu);
94
97
  return sfu;
95
98
  }
99
+ createCallSummaryMonitor(options) {
100
+ if (this._closed)
101
+ throw new Error('Cannot create a call summary monitor on a closed observer');
102
+ const existingMonitor = this._monitors.get(CallSummaryMonitor_1.CallSummaryMonitor.name);
103
+ if (existingMonitor)
104
+ return existingMonitor;
105
+ const monitor = new CallSummaryMonitor_1.CallSummaryMonitor();
106
+ const onNewCall = (call) => {
107
+ monitor.addCall(call);
108
+ call.once('close', () => {
109
+ var _a;
110
+ return setTimeout(() => {
111
+ const summary = monitor.takeSummary(call.callId);
112
+ summary && monitor.emit('summary', summary);
113
+ }, (_a = options === null || options === void 0 ? void 0 : options.timeoutAfterCallClose) !== null && _a !== void 0 ? _a : 1000);
114
+ });
115
+ };
116
+ monitor.once('close', () => {
117
+ this._monitors.delete(CallSummaryMonitor_1.CallSummaryMonitor.name);
118
+ this.off('newcall', onNewCall);
119
+ });
120
+ this._monitors.set(CallSummaryMonitor_1.CallSummaryMonitor.name, monitor);
121
+ this.on('newcall', onNewCall);
122
+ this.once('close', () => {
123
+ monitor.close();
124
+ });
125
+ return monitor;
126
+ }
127
+ createTurnUsageMonitor() {
128
+ if (this._closed)
129
+ throw new Error('Cannot create a turn usage monitor on a closed observer');
130
+ const existingMonitor = this._monitors.get(TurnUsageMonitor_1.TurnUsageMonitor.name);
131
+ if (existingMonitor)
132
+ return existingMonitor;
133
+ const monitor = new TurnUsageMonitor_1.TurnUsageMonitor();
134
+ const onNewCall = (call) => {
135
+ const onNewClient = (client) => {
136
+ const onNewPeerConnection = (pc) => {
137
+ const onUsingTurnChanged = (usingTurn) => {
138
+ if (usingTurn)
139
+ monitor.addPeerConnection(pc);
140
+ else
141
+ monitor.removePeerConnection(pc);
142
+ };
143
+ pc.once('close', () => {
144
+ pc.ICE.off('usingturnchanged', onUsingTurnChanged);
145
+ monitor.removePeerConnection(pc);
146
+ });
147
+ pc.ICE.on('usingturnchanged', onUsingTurnChanged);
148
+ };
149
+ client.once('close', () => client.off('newpeerconnection', onNewPeerConnection));
150
+ client.on('newpeerconnection', onNewPeerConnection);
151
+ };
152
+ call.once('close', () => call.off('newclient', onNewClient));
153
+ call.on('newclient', onNewClient);
154
+ };
155
+ monitor.once('close', () => {
156
+ this._monitors.delete(CallSummaryMonitor_1.CallSummaryMonitor.name);
157
+ this.off('newcall', onNewCall);
158
+ });
159
+ this._monitors.set(CallSummaryMonitor_1.CallSummaryMonitor.name, monitor);
160
+ this.on('newcall', onNewCall);
161
+ this.once('close', () => {
162
+ monitor.close();
163
+ });
164
+ return monitor;
165
+ }
96
166
  get observedCalls() {
97
167
  return this._observedCalls;
98
168
  }
@@ -1,8 +1,14 @@
1
1
  export interface ClientSummary {
2
2
  clientId: string;
3
- userId: string;
3
+ mediaUnitId: string;
4
+ userId?: string;
4
5
  joined: number;
5
6
  durationInMs: number;
7
+ avgOutboundAudioBitrate: number;
8
+ avgOutboundVideoBitrate: number;
9
+ avgInboundAudioBitrate: number;
10
+ avgInboundVideoBitrate: number;
11
+ ewmaRttInMs: number;
6
12
  }
7
13
  export interface CallSummary {
8
14
  serviceId: string;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CallSummary.d.ts","sourceRoot":"","sources":["../../src/monitors/CallSummary.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,aAAa,EAAE,CAAC;CACzB"}
@@ -0,0 +1,25 @@
1
+ /// <reference types="node" />
2
+ import { EventEmitter } from 'events';
3
+ import { CallSummary } from './CallSummary';
4
+ import { ObservedCall } from '../ObservedCall';
5
+ export type CallSummaryMonitorEvents = {
6
+ close: [];
7
+ summary: [CallSummary];
8
+ };
9
+ export declare interface CallSummaryMonitor {
10
+ on<U extends keyof CallSummaryMonitorEvents>(event: U, listener: (...args: CallSummaryMonitorEvents[U]) => void): this;
11
+ off<U extends keyof CallSummaryMonitorEvents>(event: U, listener: (...args: CallSummaryMonitorEvents[U]) => void): this;
12
+ once<U extends keyof CallSummaryMonitorEvents>(event: U, listener: (...args: CallSummaryMonitorEvents[U]) => void): this;
13
+ emit<U extends keyof CallSummaryMonitorEvents>(event: U, ...args: CallSummaryMonitorEvents[U]): boolean;
14
+ }
15
+ export declare class CallSummaryMonitor extends EventEmitter {
16
+ private readonly _summaries;
17
+ private _closed;
18
+ constructor();
19
+ addCall(call: ObservedCall): void;
20
+ private _addClient;
21
+ takeSummary(callId: string): CallSummary | undefined;
22
+ get closed(): boolean;
23
+ close(): void;
24
+ }
25
+ //# sourceMappingURL=CallSummaryMonitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CallSummaryMonitor.d.ts","sourceRoot":"","sources":["../../src/monitors/CallSummaryMonitor.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,MAAM,MAAM,wBAAwB,GAAG;IACtC,KAAK,EAAE,EAAE,CAAC;IACV,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;CACvB,CAAA;AAED,MAAM,CAAC,OAAO,WAAW,kBAAkB;IAC1C,EAAE,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACvH,GAAG,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACxH,IAAI,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACzH,IAAI,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CACxG;AAED,qBAAa,kBAAmB,SAAQ,YAAY;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAE7D,OAAO,CAAC,OAAO,CAAS;;IAKjB,OAAO,CAAC,IAAI,EAAE,YAAY;IAyBjC,OAAO,CAAC,UAAU;IAoCX,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAU3D,IAAW,MAAM,YAEhB;IAEM,KAAK;CAMZ"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CallSummaryMonitor = void 0;
4
+ const events_1 = require("events");
5
+ class CallSummaryMonitor extends events_1.EventEmitter {
6
+ constructor() {
7
+ super();
8
+ this._summaries = new Map();
9
+ this._closed = false;
10
+ }
11
+ addCall(call) {
12
+ if (this.closed)
13
+ return;
14
+ let callSummary = this._summaries.get(call.callId);
15
+ if (!callSummary) {
16
+ callSummary = {
17
+ callId: call.callId,
18
+ roomId: call.roomId,
19
+ serviceId: call.serviceId,
20
+ clients: [],
21
+ durationInMs: 0,
22
+ started: call.created,
23
+ };
24
+ this._summaries.set(call.callId, callSummary);
25
+ }
26
+ const constCallSummary = callSummary;
27
+ const onNewClient = (client) => this._addClient(constCallSummary, client);
28
+ call.once('close', () => {
29
+ call.off('newclient', onNewClient);
30
+ });
31
+ call.on('newclient', onNewClient);
32
+ }
33
+ _addClient(callSummary, client) {
34
+ if (this.closed)
35
+ return;
36
+ const clientSummary = {
37
+ clientId: client.clientId,
38
+ mediaUnitId: client.mediaUnitId,
39
+ durationInMs: 0,
40
+ avgInboundAudioBitrate: 0,
41
+ avgInboundVideoBitrate: 0,
42
+ avgOutboundAudioBitrate: 0,
43
+ avgOutboundVideoBitrate: 0,
44
+ ewmaRttInMs: 0,
45
+ joined: client.created,
46
+ userId: client.userId,
47
+ };
48
+ callSummary.clients.push(clientSummary);
49
+ const updateClient = () => {
50
+ if (client.avgRttInMs) {
51
+ clientSummary.ewmaRttInMs = (clientSummary.ewmaRttInMs * 0.9) + (client.avgRttInMs * 0.1);
52
+ }
53
+ };
54
+ client.on('update', updateClient);
55
+ client.once('close', () => {
56
+ const elapsedTimeInS = (Date.now() - client.created) / 1000;
57
+ clientSummary.avgInboundAudioBitrate = (client.totalReceivedBytes * 8) / elapsedTimeInS;
58
+ clientSummary.avgInboundVideoBitrate = (client.totalReceivedBytes * 8) / elapsedTimeInS;
59
+ clientSummary.avgOutboundAudioBitrate = (client.totalSentBytes * 8) / elapsedTimeInS;
60
+ clientSummary.avgOutboundVideoBitrate = (client.totalSentBytes * 8) / elapsedTimeInS;
61
+ clientSummary.durationInMs = elapsedTimeInS * 1000;
62
+ client.off('update', updateClient);
63
+ });
64
+ }
65
+ takeSummary(callId) {
66
+ if (this.closed)
67
+ return;
68
+ const summary = this._summaries.get(callId);
69
+ this._summaries.delete(callId);
70
+ return summary;
71
+ }
72
+ get closed() {
73
+ return this._closed;
74
+ }
75
+ close() {
76
+ if (this._closed)
77
+ return;
78
+ this._closed = true;
79
+ this.emit('close');
80
+ }
81
+ }
82
+ exports.CallSummaryMonitor = CallSummaryMonitor;
@@ -0,0 +1,32 @@
1
+ /// <reference types="node" />
2
+ import { EventEmitter } from 'events';
3
+ import { ObservedPeerConnection } from '../ObservedPeerConnection';
4
+ export type TurnUsageMonitorEvents = {
5
+ close: [];
6
+ };
7
+ export type TurnUsage = {
8
+ turnIp: string;
9
+ totalBytesSent: number;
10
+ totalBytesReceived: number;
11
+ totalPacketsSent: number;
12
+ totalPacketsReceived: number;
13
+ };
14
+ export declare interface TurnUsageMonitor {
15
+ on<U extends keyof TurnUsageMonitorEvents>(event: U, listener: (...args: TurnUsageMonitorEvents[U]) => void): this;
16
+ off<U extends keyof TurnUsageMonitorEvents>(event: U, listener: (...args: TurnUsageMonitorEvents[U]) => void): this;
17
+ once<U extends keyof TurnUsageMonitorEvents>(event: U, listener: (...args: TurnUsageMonitorEvents[U]) => void): this;
18
+ }
19
+ export declare class TurnUsageMonitor extends EventEmitter {
20
+ private readonly _connections;
21
+ private readonly _turnUsage;
22
+ private _closed;
23
+ constructor();
24
+ addPeerConnection(peerConnection: ObservedPeerConnection): void;
25
+ removePeerConnection(peerConnection: ObservedPeerConnection): void;
26
+ get turnIps(): string[];
27
+ get peerConnections(): ObservedPeerConnection[];
28
+ getUsage(turnIp: string): TurnUsage | undefined;
29
+ get closed(): boolean;
30
+ close(): void;
31
+ }
32
+ //# sourceMappingURL=TurnUsageMonitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TurnUsageMonitor.d.ts","sourceRoot":"","sources":["../../src/monitors/TurnUsageMonitor.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAGnE,MAAM,MAAM,sBAAsB,GAAG;IACpC,KAAK,EAAE,EAAE,CAAC;CACV,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;CAC7B,CAAA;AAQD,MAAM,CAAC,OAAO,WAAW,gBAAgB;IACxC,EAAE,CAAC,CAAC,SAAS,MAAM,sBAAsB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACnH,GAAG,CAAC,CAAC,SAAS,MAAM,sBAAsB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACpH,IAAI,CAAC,CAAC,SAAS,MAAM,sBAAsB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;CACrH;AAED,qBAAa,gBAAiB,SAAQ,YAAY;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;IAE3D,OAAO,CAAC,OAAO,CAAS;;IAKjB,iBAAiB,CAAC,cAAc,EAAE,sBAAsB;IAqCxD,oBAAoB,CAAC,cAAc,EAAE,sBAAsB;IAWlE,IAAW,OAAO,aAEjB;IAED,IAAW,eAAe,6BAEzB;IAEM,QAAQ,CAAC,MAAM,EAAE,MAAM;IAI9B,IAAW,MAAM,YAEhB;IAEM,KAAK;CAMZ"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TurnUsageMonitor = void 0;
4
+ const events_1 = require("events");
5
+ class TurnUsageMonitor extends events_1.EventEmitter {
6
+ constructor() {
7
+ super();
8
+ this._connections = new Map();
9
+ this._turnUsage = new Map();
10
+ this._closed = false;
11
+ }
12
+ addPeerConnection(peerConnection) {
13
+ if (this.closed)
14
+ return;
15
+ if (this._connections.has(peerConnection.peerConnectionId))
16
+ return;
17
+ const ice = peerConnection.ICE;
18
+ const onUpdate = () => {
19
+ var _a;
20
+ const turnIp = (_a = ice === null || ice === void 0 ? void 0 : ice.selectedRemoteCandidate) === null || _a === void 0 ? void 0 : _a.address;
21
+ if (!peerConnection.usingTURN || !turnIp)
22
+ return;
23
+ let usage = this._turnUsage.get(turnIp);
24
+ if (!usage) {
25
+ usage = {
26
+ turnIp,
27
+ totalBytesSent: 0,
28
+ totalBytesReceived: 0,
29
+ totalPacketsSent: 0,
30
+ totalPacketsReceived: 0
31
+ };
32
+ this._turnUsage.set(turnIp, usage);
33
+ }
34
+ usage.totalBytesSent += ice.deltaBytesSent;
35
+ usage.totalBytesReceived += ice.deltaBytesReceived;
36
+ usage.totalPacketsSent += ice.deltaPacketsSent;
37
+ usage.totalPacketsReceived += ice.deltaPacketsReceived;
38
+ };
39
+ this._connections.set(peerConnection.peerConnectionId, {
40
+ peerConnection,
41
+ onUpdate
42
+ });
43
+ peerConnection.ICE.on('update', onUpdate);
44
+ }
45
+ removePeerConnection(peerConnection) {
46
+ var _a;
47
+ if (this.closed)
48
+ return;
49
+ if (!this._connections.has(peerConnection.peerConnectionId))
50
+ return;
51
+ const { onUpdate } = (_a = this._connections.get(peerConnection.peerConnectionId)) !== null && _a !== void 0 ? _a : {};
52
+ if (!onUpdate)
53
+ return;
54
+ peerConnection.ICE.off('update', onUpdate);
55
+ }
56
+ get turnIps() {
57
+ return Array.from(this._turnUsage.keys());
58
+ }
59
+ get peerConnections() {
60
+ return Array.from(this._connections.values()).map(({ peerConnection }) => peerConnection);
61
+ }
62
+ getUsage(turnIp) {
63
+ return this._turnUsage.get(turnIp);
64
+ }
65
+ get closed() {
66
+ return this._closed;
67
+ }
68
+ close() {
69
+ if (this._closed)
70
+ return;
71
+ this._closed = true;
72
+ this.emit('close');
73
+ }
74
+ }
75
+ exports.TurnUsageMonitor = TurnUsageMonitor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@observertc/observer-js",
3
- "version": "0.40.7-beta",
3
+ "version": "0.40.9-beta",
4
4
  "description": "Server Side NodeJS Library for processing ObserveRTC Samples",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -18,9 +18,8 @@
18
18
  },
19
19
  "keywords": [
20
20
  "webrtc",
21
- "getStats",
22
21
  "observertc",
23
- "client",
22
+ "server",
24
23
  "integration"
25
24
  ],
26
25
  "author": "Balazs Kreith",
@@ -29,10 +28,6 @@
29
28
  "@bufbuild/protobuf": "1.1.1",
30
29
  "@observertc/report-schemas-js": "2.2.10",
31
30
  "@observertc/sample-schemas-js": "2.2.10",
32
- "@types/events": "^3.0.0",
33
- "@types/uuid": "^8.3.4",
34
- "ipaddr.js": "^2.1.0",
35
- "package-lock-only": "^0.0.4",
36
31
  "uuid": "^8.3.2"
37
32
  },
38
33
  "devDependencies": {
@@ -40,6 +35,8 @@
40
35
  "@types/node": "^20.9.0",
41
36
  "@typescript-eslint/eslint-plugin": "^5.38.0",
42
37
  "@typescript-eslint/parser": "^5.38.0",
38
+ "@types/events": "^3.0.0",
39
+ "@types/uuid": "^8.3.4",
43
40
  "bufferutil": "^4.0.6",
44
41
  "eslint": "^8.24.0",
45
42
  "jest": "^27.5.0",
@@ -24,7 +24,9 @@ export type ObservedClientModel= {
24
24
  };
25
25
 
26
26
  export type ObservedClientEvents = {
27
- update: [],
27
+ update: [{
28
+ elapsedTimeInMs: number,
29
+ }],
28
30
  close: [],
29
31
  newpeerconnection: [ObservedPeerConnection],
30
32
  iceconnectionstatechange: [{
@@ -713,6 +715,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
713
715
  this.inboundAudioBitrate = 0;
714
716
  this.inboundVideoBitrate = 0;
715
717
  let sumRttInMs = 0;
718
+ let anyPeerConnectionUsingTurn = false;
716
719
 
717
720
  for (const peerConnection of this._peerConnections.values()) {
718
721
  if (peerConnection.closed) continue;
@@ -732,9 +735,13 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
732
735
 
733
736
  sumRttInMs += peerConnection.avgRttInMs ?? 0;
734
737
 
735
- if (peerConnection.usingTURN) this.usingTURN = true;
738
+ anyPeerConnectionUsingTurn ||= peerConnection.usingTURN;
736
739
  }
737
740
 
741
+ this.usingTURN = anyPeerConnectionUsingTurn === true;
742
+
743
+ if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
744
+
738
745
  this.totalSentBytes += this.deltaSentAudioBytes + this.deltaSentVideoBytes;
739
746
  this.totalReceivedBytes += this.deltaReceivedAudioBytes + this.deltaReceivedVideoBytes;
740
747
  this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
@@ -753,11 +760,11 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
753
760
  this.inboundVideoBitrate = (this.deltaReceivedVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
754
761
 
755
762
  this.avgRttInMs = this._peerConnections.size ? sumRttInMs / this._peerConnections.size : undefined;
756
-
757
- if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
758
-
763
+
759
764
  this._updated = now;
760
- this.emit('update');
765
+ this.emit('update', {
766
+ elapsedTimeInMs,
767
+ });
761
768
  }
762
769
 
763
770
  private _createPeerConnection(peerConnectionId: string, label?: string): ObservedPeerConnection {
@@ -10,7 +10,9 @@ export type ObservedDataChannelModel = {
10
10
  }
11
11
 
12
12
  export type ObservedDataChannelEvents = {
13
- update: [],
13
+ update: [{
14
+ elapsedTimeInMs: number;
15
+ }],
14
16
  close: [],
15
17
  };
16
18
 
@@ -184,6 +186,8 @@ export class ObservedDataChannel extends EventEmitter {
184
186
 
185
187
  this._stats = sample;
186
188
  this._updated = timestamp;
187
- this.emit('update');
189
+ this.emit('update', {
190
+ elapsedTimeInMs,
191
+ });
188
192
  }
189
193
  }
@@ -5,13 +5,16 @@ import { IceCandidatePairReport } from '@observertc/report-schemas-js';
5
5
  import { CallMetaType, createCallMetaReport } from './common/callMetaReports';
6
6
 
7
7
  export type ObservedICEEvents = {
8
- update: [],
8
+ update: [{
9
+ elapsedTimeInMs: number;
10
+ }],
9
11
  'new-local-candidate': [IceLocalCandidate],
10
12
  'new-remote-candidate': [IceRemoteCandidate],
11
13
  'new-selected-candidate-pair': [{
12
14
  localCandidate: IceLocalCandidate,
13
15
  remoteCandidate: IceRemoteCandidate,
14
16
  }],
17
+ usingturnchanged: [boolean],
15
18
  close: [],
16
19
  };
17
20
 
@@ -32,6 +35,20 @@ export class ObservedICE extends EventEmitter {
32
35
  private readonly _localCandidates = new Map<string, IceLocalCandidate>();
33
36
  private readonly _remoteCandidates = new Map<string, IceLocalCandidate>();
34
37
 
38
+ public usingTURN = false;
39
+
40
+ public deltaBytesReceived = 0;
41
+ public deltaBytesSent = 0;
42
+ public deltaPacketsReceived = 0;
43
+ public deltaPacketsSent = 0;
44
+
45
+ public totalBytesReceived = 0;
46
+ public totalBytesSent = 0;
47
+ public totalPacketsReceived = 0;
48
+ public totalPacketsSent = 0;
49
+
50
+ public currentRttInMs?: number;
51
+
35
52
  private _selectedLocalCandidateId?: string;
36
53
  private _selectedRemoteCandidateId?: string;
37
54
  private _updated = Date.now();
@@ -131,8 +148,18 @@ export class ObservedICE extends EventEmitter {
131
148
  }
132
149
  }
133
150
 
134
- public update(candidatePair: IceCandidatePair, timestamp: number) {
151
+ public resetMetrics() {
152
+ this.deltaBytesReceived = 0;
153
+ this.deltaBytesSent = 0;
154
+ this.deltaPacketsReceived = 0;
155
+ this.deltaPacketsSent = 0;
135
156
 
157
+ this.currentRttInMs = undefined;
158
+ }
159
+
160
+ public update(candidatePair: IceCandidatePair, timestamp: number) {
161
+ const now = Date.now();
162
+ const elapsedTimeInMs = now - this._updated;
136
163
  const report: IceCandidatePairReport = {
137
164
  serviceId: this.peerConnection.client.serviceId,
138
165
  mediaUnitId: this.peerConnection.client.mediaUnitId,
@@ -149,6 +176,18 @@ export class ObservedICE extends EventEmitter {
149
176
  this.reports.addIceCandidatePairReport(report);
150
177
 
151
178
  if (!candidatePair.nominated) return;
179
+
180
+ this.deltaBytesReceived = (candidatePair.bytesReceived ?? 0) - (this._stats?.bytesReceived ?? 0);
181
+ this.deltaBytesSent = (candidatePair.bytesSent ?? 0) - (this._stats?.bytesSent ?? 0);
182
+ this.deltaPacketsReceived = (candidatePair.packetsReceived ?? 0) - (this._stats?.packetsReceived ?? 0);
183
+ this.deltaPacketsSent = (candidatePair.packetsSent ?? 0) - (this._stats?.packetsSent ?? 0);
184
+
185
+ this.totalBytesReceived += this.deltaBytesReceived;
186
+ this.totalBytesSent += this.deltaBytesSent;
187
+ this.totalPacketsReceived += this.deltaPacketsReceived;
188
+ this.totalPacketsSent += this.deltaPacketsSent;
189
+
190
+ this.currentRttInMs = candidatePair.currentRoundTripTime ? candidatePair.currentRoundTripTime * 1000 : undefined;
152
191
 
153
192
  this._stats = candidatePair;
154
193
 
@@ -167,14 +206,24 @@ export class ObservedICE extends EventEmitter {
167
206
  const remoteCandidate = this._remoteCandidates.get(candidatePair.remoteCandidateId);
168
207
 
169
208
  if (localCandidate && remoteCandidate) {
209
+ const wasUsingTURN = this.usingTURN;
210
+
211
+ this.usingTURN = remoteCandidate.candidateType?.toLocaleLowerCase() === 'relay';
212
+
170
213
  this.emit('new-selected-candidate-pair', {
171
214
  localCandidate,
172
215
  remoteCandidate,
173
216
  });
217
+
218
+ if (wasUsingTURN !== this.usingTURN) {
219
+ this.emit('usingturnchanged', this.usingTURN);
220
+ }
174
221
  }
175
222
  }
176
223
 
177
- this._updated = timestamp;
178
- this.emit('update');
224
+ this._updated = now;
225
+ this.emit('update', {
226
+ elapsedTimeInMs,
227
+ });
179
228
  }
180
229
  }
@@ -13,7 +13,9 @@ export type ObservedInboundTrackModel<K extends MediaKind> = {
13
13
  }
14
14
 
15
15
  export type ObservedInboundTrackEvents<K extends MediaKind> = {
16
- update: [],
16
+ update: [{
17
+ elapsedTimeInMs: number;
18
+ }],
17
19
  close: [],
18
20
  remoteoutboundtrack: [ObservedOutboundTrack<K>],
19
21
  };
@@ -272,7 +274,9 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
272
274
  this._stats.set(sample.ssrc, stats);
273
275
 
274
276
  this._updated = now;
275
- this.emit('update');
277
+ this.emit('update', {
278
+ elapsedTimeInMs,
279
+ });
276
280
  }
277
281
 
278
282
  public updateMetrics() {
@@ -12,7 +12,9 @@ export type ObservedOutboundTrackModel<K extends MediaKind> = {
12
12
  }
13
13
 
14
14
  export type ObservedOutboundTrackEvents = {
15
- update: [],
15
+ update: [{
16
+ elapsedTimeInMs: number;
17
+ }],
16
18
  close: [],
17
19
  };
18
20
 
@@ -242,7 +244,9 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
242
244
  }
243
245
 
244
246
  this._updated = now;
245
- this.emit('update');
247
+ this.emit('update', {
248
+ elapsedTimeInMs,
249
+ });
246
250
  }
247
251
 
248
252
  public connectInboundTrack(track: ObservedInboundTrack<Kind>) {