@observertc/observer-js 0.42.9 → 1.0.1-28c6425.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.prettierrc +6 -6
  2. package/README.md +0 -104
  3. package/lib/ObservedCall.d.ts +66 -42
  4. package/lib/ObservedCall.d.ts.map +1 -1
  5. package/lib/ObservedCall.js +232 -108
  6. package/lib/ObservedCallSummary.d.ts +18 -0
  7. package/lib/ObservedCallSummary.d.ts.map +1 -0
  8. package/lib/ObservedCertificate.d.ts +19 -0
  9. package/lib/ObservedCertificate.d.ts.map +1 -0
  10. package/lib/ObservedCertificate.js +38 -0
  11. package/lib/ObservedClient.d.ts +71 -130
  12. package/lib/ObservedClient.d.ts.map +1 -1
  13. package/lib/ObservedClient.js +311 -750
  14. package/lib/ObservedClientSummary.d.ts +23 -0
  15. package/lib/ObservedClientSummary.d.ts.map +1 -0
  16. package/lib/ObservedClientSummary.js +2 -0
  17. package/lib/ObservedCodec.d.ts +22 -0
  18. package/lib/ObservedCodec.d.ts.map +1 -0
  19. package/lib/ObservedCodec.js +45 -0
  20. package/lib/ObservedDataChannel.d.ts +23 -69
  21. package/lib/ObservedDataChannel.d.ts.map +1 -1
  22. package/lib/ObservedDataChannel.js +67 -109
  23. package/lib/ObservedIceCandidate.d.ts +29 -0
  24. package/lib/ObservedIceCandidate.d.ts.map +1 -0
  25. package/lib/ObservedIceCandidate.js +59 -0
  26. package/lib/ObservedIceCandidatePair.d.ts +44 -0
  27. package/lib/ObservedIceCandidatePair.d.ts.map +1 -0
  28. package/lib/ObservedIceCandidatePair.js +111 -0
  29. package/lib/ObservedIceTransport.d.ts +36 -0
  30. package/lib/ObservedIceTransport.d.ts.map +1 -0
  31. package/lib/ObservedIceTransport.js +93 -0
  32. package/lib/ObservedInboundRtp.d.ts +92 -0
  33. package/lib/ObservedInboundRtp.d.ts.map +1 -0
  34. package/lib/ObservedInboundRtp.js +193 -0
  35. package/lib/ObservedInboundTrack.d.ts +29 -0
  36. package/lib/ObservedInboundTrack.d.ts.map +1 -0
  37. package/lib/ObservedInboundTrack.js +57 -0
  38. package/lib/ObservedMediaPlayout.d.ts +22 -0
  39. package/lib/ObservedMediaPlayout.d.ts.map +1 -0
  40. package/lib/ObservedMediaPlayout.js +42 -0
  41. package/lib/ObservedMediaSource.d.ts +28 -0
  42. package/lib/ObservedMediaSource.d.ts.map +1 -0
  43. package/lib/ObservedMediaSource.js +55 -0
  44. package/lib/ObservedOutboundRtp.d.ts +62 -0
  45. package/lib/ObservedOutboundRtp.d.ts.map +1 -0
  46. package/lib/ObservedOutboundRtp.js +144 -0
  47. package/lib/ObservedOutboundTrack.d.ts +29 -0
  48. package/lib/ObservedOutboundTrack.d.ts.map +1 -0
  49. package/lib/ObservedOutboundTrack.js +57 -0
  50. package/lib/ObservedPeerConnection.d.ts +132 -80
  51. package/lib/ObservedPeerConnection.d.ts.map +1 -1
  52. package/lib/ObservedPeerConnection.js +630 -357
  53. package/lib/ObservedPeerConnectionTransport.d.ts +17 -0
  54. package/lib/ObservedPeerConnectionTransport.d.ts.map +1 -0
  55. package/lib/ObservedPeerConnectionTransport.js +34 -0
  56. package/lib/ObservedRemoteInboundRtp.d.ts +30 -0
  57. package/lib/ObservedRemoteInboundRtp.d.ts.map +1 -0
  58. package/lib/ObservedRemoteInboundRtp.js +60 -0
  59. package/lib/ObservedRemoteOutboundRtp.d.ts +30 -0
  60. package/lib/ObservedRemoteOutboundRtp.d.ts.map +1 -0
  61. package/lib/ObservedRemoteOutboundRtp.js +60 -0
  62. package/lib/ObservedTURN.d.ts +31 -0
  63. package/lib/ObservedTURN.d.ts.map +1 -0
  64. package/lib/ObservedTURN.js +58 -0
  65. package/lib/ObservedTurnServer.d.ts +25 -0
  66. package/lib/ObservedTurnServer.d.ts.map +1 -0
  67. package/lib/ObservedTurnServer.js +59 -0
  68. package/lib/Observer.d.ts +45 -51
  69. package/lib/Observer.d.ts.map +1 -1
  70. package/lib/Observer.js +150 -226
  71. package/lib/ObserverSummary.d.ts +13 -0
  72. package/lib/ObserverSummary.d.ts.map +1 -0
  73. package/lib/ObserverSummary.js +2 -0
  74. package/lib/common/Middleware.d.ts +10 -1
  75. package/lib/common/Middleware.d.ts.map +1 -1
  76. package/lib/common/Middleware.js +55 -36
  77. package/lib/common/logger.d.ts +8 -4
  78. package/lib/common/logger.d.ts.map +1 -1
  79. package/lib/common/logger.js +44 -68
  80. package/lib/common/types.d.ts +0 -21
  81. package/lib/common/types.d.ts.map +1 -1
  82. package/lib/common/types.js +1 -0
  83. package/lib/common/utils.d.ts +2 -1
  84. package/lib/common/utils.d.ts.map +1 -1
  85. package/lib/common/utils.js +17 -3
  86. package/lib/detectors/Detector.d.ts +5 -0
  87. package/lib/detectors/Detector.d.ts.map +1 -0
  88. package/lib/detectors/Detector.js +3 -0
  89. package/lib/detectors/Detectors.d.ts +11 -0
  90. package/lib/detectors/Detectors.d.ts.map +1 -0
  91. package/lib/detectors/Detectors.js +34 -0
  92. package/lib/index.d.ts +24 -25
  93. package/lib/index.d.ts.map +1 -1
  94. package/lib/index.js +41 -41
  95. package/lib/monitors/TurnUsageMonitor.d.ts +0 -52
  96. package/lib/monitors/TurnUsageMonitor.d.ts.map +1 -1
  97. package/lib/monitors/TurnUsageMonitor.js +145 -120
  98. package/lib/schema/ClientEventTypes.d.ts +100 -0
  99. package/lib/schema/ClientEventTypes.d.ts.map +1 -0
  100. package/lib/schema/ClientEventTypes.js +38 -0
  101. package/lib/schema/ClientMetaTypes.d.ts +34 -0
  102. package/lib/schema/ClientMetaTypes.d.ts.map +1 -0
  103. package/lib/schema/ClientMetaTypes.js +16 -0
  104. package/lib/schema/ClientSample.d.ts +1333 -0
  105. package/lib/schema/ClientSample.d.ts.map +1 -0
  106. package/lib/schema/ClientSample.js +4 -0
  107. package/lib/scores/CalculatedScore.d.ts +6 -0
  108. package/lib/scores/CalculatedScore.d.ts.map +1 -0
  109. package/lib/scores/CalculatedScore.js +2 -0
  110. package/lib/scores/DefaultCallScoreCalculator.d.ts +7 -0
  111. package/lib/scores/DefaultCallScoreCalculator.d.ts.map +1 -0
  112. package/lib/scores/DefaultCallScoreCalculator.js +21 -0
  113. package/lib/scores/ScoreCalculator.d.ts +4 -0
  114. package/lib/scores/ScoreCalculator.d.ts.map +1 -0
  115. package/lib/scores/ScoreCalculator.js +2 -0
  116. package/lib/updaters/CallUpdater.d.ts +5 -0
  117. package/lib/updaters/CallUpdater.d.ts.map +1 -0
  118. package/lib/updaters/CallUpdater.js +2 -0
  119. package/lib/updaters/ObserverUpdater.d.ts +5 -0
  120. package/lib/updaters/ObserverUpdater.d.ts.map +1 -0
  121. package/lib/updaters/ObserverUpdater.js +2 -0
  122. package/lib/updaters/OnAllCallObserverUpdater_.d.ts +13 -0
  123. package/lib/updaters/OnAllCallObserverUpdater_.d.ts.map +1 -0
  124. package/lib/updaters/OnAllCallObserverUpdater_.js +38 -0
  125. package/lib/updaters/OnAllClientCallUpdater.d.ts +13 -0
  126. package/lib/updaters/OnAllClientCallUpdater.d.ts.map +1 -0
  127. package/lib/updaters/OnAllClientCallUpdater.js +37 -0
  128. package/lib/updaters/OnAnyCallObserverUpdater_.d.ts +11 -0
  129. package/lib/updaters/OnAnyCallObserverUpdater_.d.ts.map +1 -0
  130. package/lib/updaters/OnAnyCallObserverUpdater_.js +27 -0
  131. package/lib/updaters/OnAnyClientCallUpdater.d.ts +11 -0
  132. package/lib/updaters/OnAnyClientCallUpdater.d.ts.map +1 -0
  133. package/lib/updaters/OnAnyClientCallUpdater.js +27 -0
  134. package/lib/updaters/OnIntervalUpdater.d.ts +10 -0
  135. package/lib/updaters/OnIntervalUpdater.d.ts.map +1 -0
  136. package/lib/updaters/OnIntervalUpdater.js +17 -0
  137. package/lib/updaters/Updater.d.ts +6 -0
  138. package/lib/updaters/Updater.d.ts.map +1 -0
  139. package/lib/updaters/Updater.js +2 -0
  140. package/lib/utils/RemoteTrackResolver.d.ts +7 -0
  141. package/lib/utils/RemoteTrackResolver.d.ts.map +1 -0
  142. package/lib/utils/RemoteTrackResolver.js +2 -0
  143. package/package.json +4 -4
  144. package/src/ObservedCall.ts +291 -164
  145. package/src/ObservedCallSummary.ts +22 -0
  146. package/src/ObservedCertificate.ts +43 -0
  147. package/src/ObservedClient.ts +342 -1064
  148. package/src/ObservedClientSummary.ts +24 -0
  149. package/src/ObservedCodec.ts +49 -0
  150. package/src/ObservedDataChannel.ts +68 -181
  151. package/src/ObservedIceCandidate.ts +64 -0
  152. package/src/ObservedIceCandidatePair.ts +118 -0
  153. package/src/ObservedIceTransport.ts +99 -0
  154. package/src/ObservedInboundRtp.ts +205 -0
  155. package/src/ObservedInboundTrack.ts +70 -0
  156. package/src/ObservedMediaPlayout.ts +49 -0
  157. package/src/ObservedMediaSource.ts +60 -0
  158. package/src/ObservedOutboundRtp.ts +156 -0
  159. package/src/ObservedOutboundTrack.ts +69 -0
  160. package/src/ObservedPeerConnection.ts +839 -408
  161. package/src/ObservedPeerConnectionTransport.ts +38 -0
  162. package/src/ObservedRemoteInboundRtp.ts +64 -0
  163. package/src/ObservedRemoteOutboundRtp.ts +65 -0
  164. package/src/ObservedTURN.ts +86 -0
  165. package/src/ObservedTurnServer.ts +72 -0
  166. package/src/Observer.ts +171 -294
  167. package/src/ObserverSummary.ts +15 -0
  168. package/src/common/Middleware.ts +70 -42
  169. package/src/common/logger.ts +60 -69
  170. package/src/common/types.ts +0 -42
  171. package/src/common/utils.ts +18 -2
  172. package/src/detectors/Detector.ts +6 -0
  173. package/src/detectors/Detectors.ts +38 -0
  174. package/src/index.ts +24 -90
  175. package/src/monitors/TurnUsageMonitor.ts +164 -164
  176. package/src/schema/ClientEventTypes.ts +112 -0
  177. package/src/schema/ClientMetaTypes.ts +41 -0
  178. package/src/schema/ClientSample.ts +1682 -0
  179. package/src/scores/CalculatedScore.ts +6 -0
  180. package/src/scores/DefaultCallScoreCalculator.ts +23 -0
  181. package/src/scores/ScoreCalculator.ts +3 -0
  182. package/src/updaters/CallUpdater.ts +5 -0
  183. package/src/updaters/ObserverUpdater.ts +5 -0
  184. package/src/updaters/OnAllCallObserverUpdater_.ts +46 -0
  185. package/src/updaters/OnAllClientCallUpdater.ts +46 -0
  186. package/src/updaters/OnAnyCallObserverUpdater_.ts +32 -0
  187. package/src/updaters/OnAnyClientCallUpdater.ts +32 -0
  188. package/src/updaters/OnIntervalUpdater.ts +19 -0
  189. package/src/updaters/Updater.ts +5 -0
  190. package/src/utils/RemoteTrackResolver.ts +12 -0
  191. package/tsconfig.json +3 -3
  192. package/lib/ObservedICE.d.ts +0 -64
  193. package/lib/ObservedICE.d.ts.map +0 -1
  194. package/lib/ObservedICE.js +0 -141
  195. package/lib/ObservedInboundAudioTrack.d.ts +0 -96
  196. package/lib/ObservedInboundAudioTrack.d.ts.map +0 -1
  197. package/lib/ObservedInboundAudioTrack.js +0 -217
  198. package/lib/ObservedInboundVideoTrack.d.ts +0 -101
  199. package/lib/ObservedInboundVideoTrack.d.ts.map +0 -1
  200. package/lib/ObservedInboundVideoTrack.js +0 -232
  201. package/lib/ObservedOutboundAudioTrack.d.ts +0 -87
  202. package/lib/ObservedOutboundAudioTrack.d.ts.map +0 -1
  203. package/lib/ObservedOutboundAudioTrack.js +0 -186
  204. package/lib/ObservedOutboundVideoTrack.d.ts +0 -91
  205. package/lib/ObservedOutboundVideoTrack.d.ts.map +0 -1
  206. package/lib/ObservedOutboundVideoTrack.js +0 -193
  207. package/lib/ObservedSfu.d.ts +0 -59
  208. package/lib/ObservedSfu.d.ts.map +0 -1
  209. package/lib/ObservedSfu.js +0 -157
  210. package/lib/ObservedSfuInboundRtpPad.d.ts +0 -42
  211. package/lib/ObservedSfuInboundRtpPad.d.ts.map +0 -1
  212. package/lib/ObservedSfuInboundRtpPad.js +0 -71
  213. package/lib/ObservedSfuOutboundRtpPad.d.ts +0 -42
  214. package/lib/ObservedSfuOutboundRtpPad.d.ts.map +0 -1
  215. package/lib/ObservedSfuOutboundRtpPad.js +0 -72
  216. package/lib/ObservedSfuSctpChannel.d.ts +0 -42
  217. package/lib/ObservedSfuSctpChannel.d.ts.map +0 -1
  218. package/lib/ObservedSfuSctpChannel.js +0 -57
  219. package/lib/ObservedSfuTransport.d.ts +0 -58
  220. package/lib/ObservedSfuTransport.d.ts.map +0 -1
  221. package/lib/ObservedSfuTransport.js +0 -120
  222. package/lib/RemoteTrackAssigner.d.ts +0 -14
  223. package/lib/RemoteTrackAssigner.d.ts.map +0 -1
  224. package/lib/RemoteTrackAssigner.js +0 -52
  225. package/lib/ReportsCollector.d.ts +0 -109
  226. package/lib/ReportsCollector.d.ts.map +0 -1
  227. package/lib/ReportsCollector.js +0 -207
  228. package/lib/common/CalculatedScore.d.ts +0 -80
  229. package/lib/common/CalculatedScore.d.ts.map +0 -1
  230. package/lib/common/CalculatedScore.js +0 -162
  231. package/lib/common/CallEventType.d.ts +0 -63
  232. package/lib/common/CallEventType.d.ts.map +0 -1
  233. package/lib/common/CallEventType.js +0 -39
  234. package/lib/common/CallMetaReports.d.ts +0 -42
  235. package/lib/common/CallMetaReports.d.ts.map +0 -1
  236. package/lib/common/CallMetaReports.js +0 -36
  237. package/lib/common/QualityScoreUtils.d.ts +0 -1
  238. package/lib/common/QualityScoreUtils.d.ts.map +0 -1
  239. package/lib/common/QualityScoreUtils.js +0 -1
  240. package/lib/common/TypedEventEmitter.d.ts +0 -22
  241. package/lib/common/TypedEventEmitter.d.ts.map +0 -1
  242. package/lib/common/TypedEventEmitter.js +0 -11
  243. package/lib/common/callEventReports.d.ts +0 -10
  244. package/lib/common/callEventReports.d.ts.map +0 -1
  245. package/lib/common/callEventReports.js +0 -112
  246. package/lib/monitors/CallSummary.d.ts +0 -37
  247. package/lib/monitors/CallSummary.d.ts.map +0 -1
  248. package/lib/monitors/CallSummaryMonitor.d.ts +0 -32
  249. package/lib/monitors/CallSummaryMonitor.d.ts.map +0 -1
  250. package/lib/monitors/CallSummaryMonitor.js +0 -152
  251. package/lib/monitors/ClientIssueMonitor.d.ts +0 -32
  252. package/lib/monitors/ClientIssueMonitor.d.ts.map +0 -1
  253. package/lib/monitors/ClientIssueMonitor.js +0 -49
  254. package/lib/monitors/SfuServerMonitor.d.ts +0 -79
  255. package/lib/monitors/SfuServerMonitor.d.ts.map +0 -1
  256. package/lib/monitors/SfuServerMonitor.js +0 -198
  257. package/src/ObservedICE.ts +0 -237
  258. package/src/ObservedInboundAudioTrack.ts +0 -344
  259. package/src/ObservedInboundVideoTrack.ts +0 -361
  260. package/src/ObservedOutboundAudioTrack.ts +0 -307
  261. package/src/ObservedOutboundVideoTrack.ts +0 -319
  262. package/src/ObservedSfu.ts +0 -231
  263. package/src/ObservedSfuInboundRtpPad.ts +0 -106
  264. package/src/ObservedSfuOutboundRtpPad.ts +0 -107
  265. package/src/ObservedSfuSctpChannel.ts +0 -103
  266. package/src/ObservedSfuTransport.ts +0 -185
  267. package/src/RemoteTrackAssigner.ts +0 -68
  268. package/src/ReportsCollector.ts +0 -336
  269. package/src/common/CalculatedScore.ts +0 -188
  270. package/src/common/CallEventType.ts +0 -69
  271. package/src/common/CallMetaReports.ts +0 -82
  272. package/src/common/QualityScoreUtils.ts +0 -0
  273. package/src/common/TypedEventEmitter.ts +0 -32
  274. package/src/common/callEventReports.ts +0 -178
  275. package/src/monitors/CallSummary.ts +0 -38
  276. package/src/monitors/CallSummaryMonitor.ts +0 -192
  277. package/src/monitors/ClientIssueMonitor.ts +0 -88
  278. package/src/monitors/SfuServerMonitor.ts +0 -285
  279. /package/lib/{monitors/CallSummary.js → ObservedCallSummary.js} +0 -0
@@ -1,109 +1,77 @@
1
- import { Browser, ClientSample, Engine, IceLocalCandidate, IceRemoteCandidate, MediaCodecStats, MediaDevice, OperationSystem, Platform } from '@observertc/sample-schemas-js';
2
- import { ObservedCall } from './ObservedCall';
3
1
  import { EventEmitter } from 'events';
4
2
  import { ObservedPeerConnection } from './ObservedPeerConnection';
5
3
  import { createLogger } from './common/logger';
6
- import { CallMetaType, createCallMetaReport } from './common/CallMetaReports';
7
4
  // eslint-disable-next-line camelcase
8
- import { PartialBy, isValidUuid } from './common/utils';
9
- import { CallEventType } from './common/CallEventType';
10
- import { ObservedSfu } from './ObservedSfu';
11
- import { ClientIssue } from './monitors/CallSummary';
12
- import { CallEventReport } from '@observertc/report-schemas-js';
13
- import { CalculatedScore } from './common/CalculatedScore';
5
+ import { ClientEvent, ClientMetaData, ClientSample, PeerConnectionSample, ClientIssue, ExtensionStat } from './schema/ClientSample';
6
+ import * as MetaData from './schema/ClientMetaTypes';
7
+ import { ClientEventTypes } from './schema/ClientEventTypes';
8
+ import { ObservedCall } from './ObservedCall';
9
+ import { ClientMetaTypes } from './schema/ClientMetaTypes';
10
+ import { getMedian, parseJsonAs } from './common/utils';
11
+ import { CalculatedScore } from './scores/CalculatedScore';
12
+ import { Detectors } from './detectors/Detectors';
13
+ import { ObservedClientSummary } from './ObservedClientSummary';
14
14
 
15
15
  const logger = createLogger('ObservedClient');
16
16
 
17
- export type ObservedClientModel= {
17
+ export type ObservedClientSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = {
18
18
  clientId: string;
19
- mediaUnitId: string;
20
- userId?: string,
21
- marker?: string,
22
- operationSystem?: OperationSystem,
23
- engine?: Engine,
24
- platform?: Platform,
25
- browser?: Browser,
26
- coordinates?: {
27
- latitude: number;
28
- longitude: number;
29
- },
19
+ appData?: AppData;
30
20
  };
31
21
 
32
22
  export type ObservedClientEvents = {
33
- update: [{
34
- sample: ClientSample,
35
- elapsedTimeInMs: number,
36
- }],
37
- close: [],
38
- joined: [],
39
- left: [],
40
- newpeerconnection: [ObservedPeerConnection],
41
- iceconnectionstatechange: [{
42
- peerConnection: ObservedPeerConnection,
43
- state: string,
44
- }],
45
- icegatheringstatechange: [{
46
- peerConnection: ObservedPeerConnection,
47
- state: string,
48
- }],
49
- connectionstatechange: [{
50
- peerConnection: ObservedPeerConnection,
51
- state: string,
52
- }],
53
- selectedcandidatepair: [{
54
- peerConnection: ObservedPeerConnection,
55
- localCandidate: IceLocalCandidate,
56
- remoteCandidate: IceRemoteCandidate,
57
- }],
58
- issue: [ClientIssue],
59
- usingturn: [boolean],
60
- usermediaerror: [string],
61
- rejoined: [{
62
- lastJoined: number,
63
- }],
64
- score: [CalculatedScore],
23
+ update: [
24
+ {
25
+ sample: ClientSample;
26
+ elapsedTimeInMs: number;
27
+ }
28
+ ];
29
+ close: [];
30
+ joined: [];
31
+ issue: [ClientIssue];
32
+ metaData: [ClientMetaData];
33
+ rejoined: [timestamp: number];
34
+ left: [];
35
+ usingturn: [boolean];
36
+ usermediaerror: [string];
37
+
38
+ // do we need this?
39
+ newpeerconnection: [ObservedPeerConnection];
65
40
  };
66
41
 
67
- export declare interface ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> {
42
+ export declare interface ObservedClient {
68
43
  on<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
69
44
  off<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
70
45
  once<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
71
46
  emit<U extends keyof ObservedClientEvents>(event: U, ...args: ObservedClientEvents[U]): boolean;
72
- readonly appData: AppData;
73
47
  }
74
48
 
75
- type PendingPeerConnectionTimestamp = {
76
- type: 'opened' | 'closed'
77
- peerConnectionId: string;
78
- timestamp: number;
79
- }
49
+ export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
50
+ public readonly detectors: Detectors;
80
51
 
81
- type PendingMediaTrackTimestamp = {
82
- type: 'added' | 'removed'
83
- peerConnectionId: string;
84
- mediaTrackId: string;
85
- timestamp: number;
86
- }
52
+ public readonly clientId: string;
53
+ public readonly observedPeerConnections = new Map<string, ObservedPeerConnection>();
54
+ public readonly calculatedScore: CalculatedScore = {
55
+ weight: 1,
56
+ value: undefined,
57
+ };
58
+
59
+ public appData: AppData;
60
+ public attachments?: Record<string, unknown>;
87
61
 
88
- export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
89
-
90
- public readonly created = Date.now();
91
62
  public updated = Date.now();
92
- public sfuId?: string;
93
-
94
- private readonly _peerConnections = new Map<string, ObservedPeerConnection>();
95
-
96
- private _closed = false;
97
-
98
- private _acceptedSamples = 0;
99
- private _timeZoneOffsetInHours?: number;
63
+ public acceptedSamples = 0;
64
+ public closed = false;
100
65
 
66
+ public joinedAt?: number;
67
+ public leftAt?: number;
101
68
  // the timestamp of the CLIENT_JOINED event
102
- private _joined?: number;
103
- private _left?: number;
104
- public lastStatsTimestamp?: number;
69
+ public operationSystem?: MetaData.OperationSystem;
70
+ public engine?: MetaData.Engine;
71
+ public platform?: MetaData.Platform;
72
+ public browser?: MetaData.Browser;
73
+ public mediaConstraints: string[] = [];
105
74
 
106
- public score?: CalculatedScore;
107
75
  public usingTURN = false;
108
76
  public availableOutgoingBitrate = 0;
109
77
  public availableIncomingBitrate = 0;
@@ -112,12 +80,14 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
112
80
  public totalOutboundPacketsSent = 0;
113
81
  public totalDataChannelBytesSent = 0;
114
82
  public totalDataChannelBytesReceived = 0;
115
- public totalSentBytes = 0;
116
- public totalReceivedBytes = 0;
83
+ public totalDataChannelMessagesSent = 0;
84
+ public totalDataChannelMessagesReceived = 0;
117
85
  public totalReceivedAudioBytes = 0;
118
86
  public totalReceivedVideoBytes = 0;
119
87
  public totalSentAudioBytes = 0;
120
88
  public totalSentVideoBytes = 0;
89
+ public totalSentBytes = 0;
90
+ public totalReceivedBytes = 0;
121
91
 
122
92
  public deltaReceivedAudioBytes = 0;
123
93
  public deltaReceivedVideoBytes = 0;
@@ -125,1047 +95,355 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
125
95
  public deltaSentVideoBytes = 0;
126
96
  public deltaDataChannelBytesSent = 0;
127
97
  public deltaDataChannelBytesReceived = 0;
98
+ public deltaDataChannelMessagesSent = 0;
99
+ public deltaDataChannelMessagesReceived = 0;
128
100
  public deltaInboundPacketsLost = 0;
129
101
  public deltaInboundPacketsReceived = 0;
130
102
  public deltaOutboundPacketsSent = 0;
131
-
132
- public avgRttInMs?: number;
133
- public outboundAudioBitrate?: number;
134
- public outboundVideoBitrate?: number;
135
- public inboundAudioBitrate?: number;
136
- public inboundVideoBitrate?: number;
137
- public mediaConstraints: string[] = [];
138
-
139
- public readonly mediaDevices: MediaDevice[] = [];
140
- public readonly mediaCodecs: MediaCodecStats[] = [];
141
- public readonly userMediaErrors: string[] = [];
103
+ public deltaRttInMsMeasurements: number[] = [];
104
+ public deltaTransportSentBytes = 0;
105
+ public deltaTransportReceivedBytes = 0;
106
+ public deltaRttLt50Measurements = 0;
107
+ public deltaRttLt150Measurements = 0;
108
+ public deltaRttLt300Measurements = 0;
109
+ public deltaRttGtOrEq300Measurements = 0;
110
+
111
+ public currentRttInMs?: number;
112
+ public sendingAudioBitrate = 0;
113
+ public sendingVideoBitrate = 0;
114
+ public receivingAudioBitrate = 0;
115
+ public receivingVideoBitrate = 0;
116
+
117
+ public numberOfInboundRtpStreams = 0;
118
+ public numberOfInbundTracks = 0;
119
+ public numberOfOutboundRtpStreams = 0;
120
+ public numberOfOutboundTracks = 0;
121
+ public numberOfDataChannels = 0;
122
+
123
+ public totalRttLt50Measurements = 0;
124
+ public totalRttLt150Measurements = 0;
125
+ public totalRttLt300Measurements = 0;
126
+ public totalRttGtOrEq300Measurements = 0;
127
+ public deltaNumberOfIssues = 0;
128
+
129
+ public totalScoreSum = 0;
130
+ public numberOfScoreMeasurements = 0;
131
+ public numberOfIssues = 0;
132
+
133
+ public readonly mediaDevices: MetaData.MediaDeviceInfo[] = [];
142
134
  public issues: ClientIssue[] = [];
143
135
 
144
- public ωpendingPeerConnectionTimestamps: PendingPeerConnectionTimestamp[] = [];
145
- public ωpendingMediaTrackTimestamps: PendingMediaTrackTimestamp[] = [];
146
-
147
- public constructor(
148
- private readonly _model: ObservedClientModel,
149
- public readonly call: ObservedCall,
150
- public readonly appData: AppData
151
- ) {
136
+ public constructor(settings: ObservedClientSettings<AppData>, public readonly call: ObservedCall) {
152
137
  super();
153
138
  this.setMaxListeners(Infinity);
154
- }
155
-
156
- public getSfu<T extends Record<string, unknown> = Record<string, unknown>>(): ObservedSfu<T> | undefined {
157
- const sfu = this.call.observer.observedSfus.get(this.sfuId ?? '');
158
139
 
159
- if (!sfu) return;
140
+ this.clientId = settings.clientId;
141
+ this.appData = settings.appData ?? {} as AppData;
160
142
 
161
- return sfu as ObservedSfu<T>;
143
+ this.detectors = new Detectors();
162
144
  }
163
-
164
- public get joined() {
165
- return this._joined;
166
- }
167
-
168
- public set joined(value: number | undefined) {
169
- this._joined = value;
170
-
171
- if (this._joined) {
172
- this.emit('joined');
173
- }
174
- }
175
-
176
- public get left() {
177
- return this._left;
178
- }
179
-
180
- public set left(value: number | undefined) {
181
- this._left = value;
182
-
183
- if (this._left) {
184
- this.emit('left');
185
- }
186
- }
187
-
188
- public get sfu(): ObservedSfu | undefined {
189
- if (!this.sfuId) return;
190
-
191
- return this.call.observer.observedSfus.get(this.sfuId);
192
- }
193
-
194
- public get coordinates() {
195
- return this._model.coordinates;
196
- }
197
-
198
- public set coordinates(value: { latitude: number; longitude: number } | undefined) {
199
- this._model.coordinates = value;
200
- }
201
-
202
- public get clientId(): string {
203
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
- return this._model.clientId!;
205
- }
206
-
207
- public get roomId() {
208
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
209
- return this.call.roomId!;
210
- }
211
-
212
- public get serviceId() {
213
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
214
- return this.call.serviceId!;
215
- }
216
-
217
- public get callId() {
218
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219
- return this.call.callId!;
220
- }
221
-
222
- public get mediaUnitId() {
223
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
224
- return this._model.mediaUnitId!;
225
- }
226
-
227
- public get reports() {
228
- return this.call.reports;
229
- }
230
-
231
- public get userId() {
232
- return this._model.userId;
233
- }
234
-
235
- public set userId(userId: string | undefined) {
236
- this._model.userId = userId;
237
- }
238
-
239
- public get timeZoneOffsetInHours() {
240
- return this._timeZoneOffsetInHours;
241
- }
242
-
243
- public get marker() {
244
- return this._model.marker;
245
- }
246
-
247
- public get browser() {
248
- return this._model.browser;
249
- }
250
-
251
- public get engine() {
252
- return this._model.engine;
253
- }
254
-
255
- public get operationSystem() {
256
- return this._model.operationSystem;
257
- }
258
-
259
- public get platform() {
260
- return this._model.platform;
261
- }
262
-
263
- public get acceptedSamples() {
264
- return this._acceptedSamples;
145
+
146
+ public get numberOfPeerConnections() {
147
+ return this.observedPeerConnections.size;
265
148
  }
266
149
 
267
- public get closed() {
268
- return this._closed;
150
+ public get score() {
151
+ return this.calculatedScore.value;
269
152
  }
270
153
 
271
- public get peerConnections(): ReadonlyMap<string, ObservedPeerConnection> {
272
- return this._peerConnections;
273
- }
274
-
275
154
  public close() {
276
- if (this._closed) return;
277
- this._closed = true;
155
+ if (this.closed) return;
156
+ this.closed = true;
278
157
 
279
- Array.from(this._peerConnections.values()).forEach((peerConnection) => peerConnection.close());
158
+ Array.from(this.observedPeerConnections.values()).forEach((peerConnection) => peerConnection.close());
280
159
 
281
160
  this.emit('close');
282
161
  }
283
162
 
284
- public addEventReport(params: PartialBy<Omit<CallEventReport, 'serviceId' | 'roomId' | 'callId' | 'clientId' | 'userId' | 'marker' | 'attachments'>, 'timestamp'> & { attachments?: Record<string, unknown> }) {
285
- const {
286
- attachments,
287
- ...fields
288
- } = params;
289
-
290
- this.reports.addCallEventReport({
291
- ...fields,
292
- serviceId: this.serviceId,
293
- mediaUnitId: this.mediaUnitId,
294
- roomId: this.roomId,
295
- callId: this.callId,
296
- clientId: this.clientId,
297
- userId: this.userId,
298
- timestamp: params.timestamp ?? Date.now(),
299
- marker: this.marker,
300
- attachments: attachments ? JSON.stringify(attachments) : undefined,
301
- });
302
- }
303
-
304
- public addExtensionStatsReport(extensionType: string, payload?: Record<string, unknown>) {
305
- this.reports.addClientExtensionReport({
306
- serviceId: this.serviceId,
307
- mediaUnitId: this.mediaUnitId,
308
- roomId: this.roomId,
309
- callId: this.callId,
310
- clientId: this.clientId,
311
- userId: this.userId,
312
- timestamp: Date.now(),
313
- extensionType,
314
- payload: JSON.stringify(payload),
315
- marker: this.marker,
316
- });
317
- }
318
-
319
- public addIssue(issue: ClientIssue) {
320
- try {
321
-
322
- this.reports.addCallEventReport({
323
- serviceId: this.serviceId,
324
- mediaUnitId: this.mediaUnitId,
325
- roomId: this.roomId,
326
- callId: this.callId,
327
- clientId: this.clientId,
328
- userId: this.userId,
329
-
330
- name: CallEventType.CLIENT_ISSUE,
331
- value: issue.severity,
332
- peerConnectionId: issue.peerConnectionId,
333
- mediaTrackId: issue.trackId,
334
- message: issue.description,
335
- timestamp: issue.timestamp ?? Date.now(),
336
- attachments: issue.attachments ? JSON.stringify(issue.attachments): undefined,
337
- });
338
- } catch (err) {
339
- logger.warn(`Error adding client issue: ${(err as Error)?.message}`);
340
- }
341
-
342
- this._addAndEmitIssue(issue);
343
- }
344
-
345
163
  public accept(sample: ClientSample): void {
346
- if (this._closed) throw new Error(`Client ${this.clientId} is closed`);
347
- if (sample.clientId && sample.clientId !== 'NULL' && sample.clientId !== this.clientId) {
348
- throw new Error(`Sample client id (${sample.clientId}) does not match the client id of the observed client (${this.clientId})`);
349
- }
350
- const now = Date.now();
164
+ if (this.closed) throw new Error(`Client ${this.clientId} is closed`);
351
165
 
352
- ++this._acceptedSamples;
353
- if (0 < this.issues.length) {
354
- // we reset the issues every time we accept a new sample
355
- this.issues = [];
356
- }
357
- for (const peerConnection of this._peerConnections.values()) {
358
- if (peerConnection.closed) continue;
359
- peerConnection.resetMetrics();
360
- }
166
+ const now = Date.now();
167
+ const elapsedInMs = now - this.updated;
168
+ const elapsedInSeconds = elapsedInMs / 1000;
361
169
 
362
- if (this._model.userId) {
363
- if (sample.userId && sample.userId !== 'NULL' && sample.userId !== this._model.userId) {
364
- this._model.userId = sample.userId;
365
- }
366
- } else if (sample.userId && sample.userId !== 'NULL') {
367
- this._model.userId = sample.userId;
368
- }
170
+ ++this.acceptedSamples;
369
171
 
370
- if (this._model.marker !== sample.marker) {
371
- this._model.marker = sample.marker;
372
- this._peerConnections.forEach((peerConnection) => (peerConnection.marker = sample.marker));
373
- }
374
-
375
- if (this._timeZoneOffsetInHours !== sample.timeZoneOffsetInHours) {
376
- this._timeZoneOffsetInHours = sample.timeZoneOffsetInHours;
377
- }
378
-
379
- if (sample.os && (
380
- this._model.operationSystem?.name !== sample.os.name ||
381
- this._model.operationSystem?.version !== sample.os.version ||
382
- this._model.operationSystem?.versionName !== sample.os.versionName
383
- )) {
384
- this._model.operationSystem = sample.os;
385
-
386
- const callMetaReport = createCallMetaReport(
387
- this.serviceId,
388
- this.mediaUnitId,
389
- this.roomId,
390
- this.callId,
391
- this.clientId,
392
- {
393
- type: CallMetaType.OPERATION_SYSTEM,
394
- payload: sample.os,
395
- },
396
- this.userId,
397
- undefined,
398
- sample.timestamp
399
- );
400
-
401
- this.reports.addCallMetaReport(callMetaReport);
402
- }
403
-
404
- if (sample.engine && (
405
- this._model.engine?.name !== sample.engine.name ||
406
- this._model.engine?.version !== sample.engine.version
407
- )) {
408
- this._model.engine = sample.engine;
409
-
410
- const callMetaReport = createCallMetaReport(
411
- this.serviceId,
412
- this.mediaUnitId,
413
- this.roomId,
414
- this.callId,
415
- this.clientId,
416
- {
417
- type: CallMetaType.ENGINE,
418
- payload: sample.engine,
419
- },
420
- this.userId,
421
- undefined,
422
- sample.timestamp
423
- );
424
-
425
- this.reports.addCallMetaReport(callMetaReport);
426
- }
427
-
428
- if (sample.platform && (
429
- this._model.platform?.model !== sample.platform.model ||
430
- this._model.platform?.type !== sample.platform.type ||
431
- this._model.platform?.vendor !== sample.platform.vendor
432
- )) {
433
- this._model.platform = sample.platform;
434
-
435
- const callMetaReport = createCallMetaReport(
436
- this.serviceId,
437
- this.mediaUnitId,
438
- this.roomId,
439
- this.callId,
440
- this.clientId,
441
- {
442
- type: CallMetaType.PLATFORM,
443
- payload: sample.platform,
444
- },
445
- this.userId,
446
- undefined,
447
- sample.timestamp
448
- );
449
-
450
- this.reports.addCallMetaReport(callMetaReport);
451
- }
452
-
453
- if (sample.browser && (
454
- this._model.browser?.name !== sample.browser.name ||
455
- this._model.browser?.version !== sample.browser.version
456
- )) {
457
- this._model.browser = sample.browser;
458
-
459
- const callMetaReport = createCallMetaReport(
460
- this.serviceId,
461
- this.mediaUnitId,
462
- this.roomId,
463
- this.callId,
464
- this.clientId,
465
- {
466
- type: CallMetaType.BROWSER,
467
- payload: sample.browser,
468
- },
469
- this.userId,
470
- undefined,
471
- sample.timestamp
472
- );
473
-
474
- this.reports.addCallMetaReport(callMetaReport);
475
- }
476
-
477
- for (const mediaConstraint of sample.mediaConstraints ?? []) {
478
- const callMetaReport = createCallMetaReport(
479
- this.serviceId,
480
- this.mediaUnitId,
481
- this.roomId,
482
- this.callId,
483
- this.clientId,
484
- {
485
- type: CallMetaType.MEDIA_CONSTRAINT,
486
- payload: mediaConstraint,
487
- },
488
- this.userId,
489
- undefined,
490
- sample.timestamp
491
- );
492
-
493
- this.reports.addCallMetaReport(callMetaReport);
494
- this.mediaConstraints.push(mediaConstraint);
495
- }
496
-
497
- for (const localSDP of sample.localSDPs ?? []) {
498
- const callMetaReport = createCallMetaReport(
499
- this.serviceId,
500
- this.mediaUnitId,
501
- this.roomId,
502
- this.callId,
503
- this.clientId,
504
- {
505
- type: CallMetaType.LOCAL_SDP,
506
- payload: localSDP,
507
- },
508
- this.userId,
509
- undefined,
510
- sample.timestamp
511
- );
512
-
513
- this.reports.addCallMetaReport(callMetaReport);
514
- }
515
-
516
- for (const extensionStats of sample.extensionStats ?? []) {
517
- this.reports.addClientExtensionReport({
518
- serviceId: this.serviceId,
519
- mediaUnitId: this.mediaUnitId,
520
- roomId: this.roomId,
521
- callId: this.callId,
522
- clientId: this.clientId,
523
- userId: this.userId,
524
- timestamp: now,
525
- payload: extensionStats.payload,
526
- extensionType: extensionStats.type,
527
- });
528
- }
529
-
530
- for (const { timestamp, ...callEvent } of sample.customCallEvents ?? []) {
531
- switch (callEvent.name) {
532
- case CallEventType.CLIENT_JOINED: {
533
- const lastJoined = this.joined;
534
-
535
- this.joined = timestamp;
536
-
537
- // if it is joined before and it is joined again
538
- if (lastJoined && this.joined && lastJoined !== this.joined) {
539
- this.emit('rejoined', { lastJoined });
540
- }
541
- // in case we have a left event before the joined event
542
- if (this.left && this.joined && this.left < this.joined) {
543
- this.left = undefined;
544
- }
545
- break;
546
- }
547
- case CallEventType.CLIENT_LEFT: {
548
- this.left = timestamp;
549
- break;
550
- }
551
- case CallEventType.PEER_CONNECTION_OPENED: {
552
- callEvent.peerConnectionId && this.ωpendingPeerConnectionTimestamps.push({
553
- type: 'opened',
554
- peerConnectionId: callEvent.peerConnectionId,
555
- timestamp: timestamp ?? sample.timestamp,
556
- });
557
- break;
558
- }
559
- case CallEventType.PEER_CONNECTION_CLOSED: {
560
- callEvent.peerConnectionId && this.ωpendingPeerConnectionTimestamps.push({
561
- type: 'closed',
562
- peerConnectionId: callEvent.peerConnectionId,
563
- timestamp: timestamp ?? sample.timestamp,
564
- });
565
- break;
566
- }
567
- case CallEventType.MEDIA_TRACK_ADDED: {
568
- callEvent.peerConnectionId && callEvent.mediaTrackId && this.ωpendingMediaTrackTimestamps.push({
569
- type: 'added',
570
- peerConnectionId: callEvent.peerConnectionId,
571
- mediaTrackId: callEvent.mediaTrackId,
572
- timestamp: timestamp ?? sample.timestamp,
573
- });
574
- break;
575
- }
576
- case CallEventType.MEDIA_TRACK_REMOVED: {
577
- callEvent.peerConnectionId && callEvent.mediaTrackId && this.ωpendingMediaTrackTimestamps.push({
578
- type: 'removed',
579
- peerConnectionId: callEvent.peerConnectionId,
580
- mediaTrackId: callEvent.mediaTrackId,
581
- timestamp: timestamp ?? sample.timestamp,
582
- });
583
- break;
584
- }
585
- case CallEventType.CLIENT_ISSUE: {
586
- const severity = callEvent.value ? callEvent.value as ClientIssue['severity'] : 'minor';
587
-
588
- try {
589
- const issue: ClientIssue = {
590
- severity,
591
- timestamp: timestamp ?? Date.now(),
592
- description: callEvent.message,
593
- peerConnectionId: callEvent.peerConnectionId,
594
- trackId: callEvent.mediaTrackId,
595
- attachments: callEvent.attachments ? JSON.parse(callEvent.attachments) : undefined,
596
- };
597
-
598
- this._addAndEmitIssue(issue);
599
- } catch (err) {
600
- logger.warn(`Error parsing client issue: ${(err as Error)?.message}`);
601
- }
602
- break;
603
- }
604
- }
605
-
606
- this.reports.addCallEventReport({
607
- serviceId: this.serviceId,
608
- mediaUnitId: this.mediaUnitId,
609
- roomId: this.roomId,
610
- callId: this.callId,
611
- clientId: this.clientId,
612
- userId: this.userId,
613
- timestamp: timestamp ?? now,
614
- ...callEvent,
615
- });
616
-
617
- this.call.emit('callevent', {
618
- mediaUnitId: this.mediaUnitId,
619
- clientId: this.clientId,
620
- userId: this.userId,
621
- timestamp: timestamp ?? now,
622
- ...callEvent,
623
- });
624
-
625
- }
626
-
627
- for (const userMediaError of sample.userMediaErrors ?? []) {
628
- const callMetaReport = createCallMetaReport(
629
- this.serviceId,
630
- this.mediaUnitId,
631
- this.roomId,
632
- this.callId,
633
- this.clientId, {
634
- type: CallMetaType.USER_MEDIA_ERROR,
635
- payload: userMediaError,
636
- },
637
- this.userId,
638
- );
639
-
640
- this.reports.addCallMetaReport(callMetaReport);
641
- this.userMediaErrors.push(userMediaError);
642
- this.emit('usermediaerror', userMediaError);
643
- }
644
-
645
- for (const certificate of sample.certificates ?? []) {
646
- const callMetaReport = createCallMetaReport(
647
- this.serviceId,
648
- this.mediaUnitId,
649
- this.roomId,
650
- this.callId,
651
- this.clientId, {
652
- type: CallMetaType.CERTIFICATE,
653
- payload: certificate,
654
- },
655
- this.userId
656
- );
657
-
658
- this.reports.addCallMetaReport(callMetaReport);
659
- }
660
-
661
- for (const codec of sample.codecs ?? []) {
662
- const callMetaReport = createCallMetaReport(
663
- this.serviceId,
664
- this.mediaUnitId,
665
- this.roomId,
666
- this.callId,
667
- this.clientId, {
668
- type: CallMetaType.CODEC,
669
- payload: codec,
670
- }, this.userId);
671
-
672
- this.reports.addCallMetaReport(callMetaReport);
673
- if (codec.mimeType && !this.mediaCodecs.find((c) => c.mimeType === codec.mimeType)) {
674
- this.mediaCodecs.push(codec);
675
- }
676
- }
677
-
678
- for (const iceServer of sample.iceServers ?? []) {
679
- const callMetaReport = createCallMetaReport(
680
- this.serviceId,
681
- this.mediaUnitId,
682
- this.roomId,
683
- this.callId,
684
- this.clientId, {
685
- type: CallMetaType.ICE_SERVER,
686
- payload: iceServer,
687
- }, this.userId);
688
-
689
- this.reports.addCallMetaReport(callMetaReport);
690
- }
691
-
692
- for (const mediaDevice of sample.mediaDevices ?? []) {
693
- const callMetaReport = createCallMetaReport(
694
- this.serviceId,
695
- this.mediaUnitId,
696
- this.roomId,
697
- this.callId,
698
- this.clientId, {
699
- type: CallMetaType.MEDIA_DEVICE,
700
- payload: mediaDevice,
701
- }, this.userId);
702
-
703
- this.reports.addCallMetaReport(callMetaReport);
704
- if (mediaDevice.id && !this.mediaDevices.find((d) => d.id === mediaDevice.id)) {
705
- this.mediaDevices.push(mediaDevice);
706
- }
707
- }
708
-
709
- for (const mediaSource of sample.mediaSources ?? []) {
710
- const callMetaReport = createCallMetaReport(
711
- this.serviceId,
712
- this.mediaUnitId,
713
- this.roomId,
714
- this.callId,
715
- this.clientId, {
716
- type: CallMetaType.MEDIA_SOURCE,
717
- payload: mediaSource,
718
- }, this.userId);
719
-
720
- this.reports.addCallMetaReport(callMetaReport);
721
- }
722
-
723
- for (const transport of sample.pcTransports ?? []) {
724
- try {
725
- const peerConnection = this._peerConnections.get(transport.transportId) ?? this._createPeerConnection(transport.peerConnectionId);
726
-
727
- !peerConnection.label && transport.label && (peerConnection.label = transport.label);
728
- peerConnection.update(transport, sample.timestamp);
729
- } catch (err) {
730
- logger.error(`Error creating peer connection: ${(err as Error)?.message}`);
731
- }
732
-
733
- }
734
-
735
- for (const track of sample.inboundAudioTracks ?? []) {
736
- if (!track.peerConnectionId || !track.trackId) {
737
- logger.warn(`InboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
172
+ this.availableIncomingBitrate = 0;
173
+ this.availableOutgoingBitrate = 0;
174
+ this.deltaDataChannelBytesReceived = 0;
175
+ this.deltaDataChannelBytesSent = 0;
176
+ this.deltaDataChannelMessagesReceived = 0;
177
+ this.deltaDataChannelMessagesSent = 0;
178
+ this.deltaInboundPacketsLost = 0;
179
+ this.deltaInboundPacketsReceived = 0;
180
+ this.deltaOutboundPacketsSent = 0;
181
+ this.deltaReceivedAudioBytes = 0;
182
+ this.deltaReceivedVideoBytes = 0;
183
+ this.deltaSentAudioBytes = 0;
184
+ this.deltaSentVideoBytes = 0;
185
+ this.deltaTransportReceivedBytes = 0;
186
+ this.deltaTransportSentBytes = 0;
187
+ this.deltaRttInMsMeasurements = [];
188
+ this.deltaRttLt50Measurements = 0;
189
+ this.deltaRttLt150Measurements = 0;
190
+ this.deltaRttLt300Measurements = 0;
191
+ this.deltaRttGtOrEq300Measurements = 0;
192
+ this.deltaNumberOfIssues = 0;
193
+
194
+ this.numberOfDataChannels = 0;
195
+ this.numberOfInboundRtpStreams = 0;
196
+ this.numberOfInbundTracks = 0;
197
+ this.numberOfOutboundRtpStreams = 0;
198
+ this.numberOfOutboundTracks = 0;
199
+
200
+ sample.clientEvents?.forEach(this._processClientEvent.bind(this));
201
+ sample.clientMetaItems?.forEach(this.addMetadata.bind(this));
202
+ sample.clientIssues?.forEach(this.addIssue.bind(this));
203
+ sample.peerConnections?.forEach(this._updatePeerConnection.bind(this));
204
+ sample.extensionStats?.forEach(this.addExtensionStats.bind(this));
738
205
 
739
- continue;
740
- }
206
+ this._updateRttMeasurements();
741
207
 
742
- try {
743
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
208
+ // emit new attachments?
209
+ this.attachments = sample.attachments;
744
210
 
745
- if (!peerConnection) continue;
746
-
747
- const inboundAudioTrack = peerConnection.inboundAudioTracks.get(track.trackId) ?? peerConnection.createInboundAudioTrack({
748
- trackId: track.trackId,
749
- sfuStreamId: track.sfuStreamId,
750
- sfuSinkId: track.sfuSinkId,
751
- });
752
-
753
- inboundAudioTrack.update(track, sample.timestamp);
754
-
755
- if (!inboundAudioTrack.remoteOutboundTrack) {
756
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundAudioTrack.get(inboundAudioTrack.sfuStreamId ?? '');
211
+ this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
212
+ this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
213
+ this.totalDataChannelMessagesReceived += this.deltaDataChannelMessagesReceived;
214
+ this.totalDataChannelMessagesSent += this.deltaDataChannelMessagesSent;
215
+ this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
216
+ this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
217
+ this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
218
+ this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
219
+ this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
220
+ this.totalSentAudioBytes += this.deltaSentAudioBytes;
221
+ this.totalSentVideoBytes += this.deltaSentVideoBytes;
222
+ this.totalReceivedBytes += this.deltaTransportReceivedBytes;
223
+ this.totalSentBytes += this.deltaTransportSentBytes;
224
+ this.totalRttLt50Measurements += this.deltaRttLt50Measurements;
225
+ this.totalRttLt150Measurements += this.deltaRttLt150Measurements;
226
+ this.totalRttLt300Measurements += this.deltaRttLt300Measurements;
227
+ this.totalRttGtOrEq300Measurements += this.deltaRttGtOrEq300Measurements;
228
+ this.numberOfIssues += this.deltaNumberOfIssues;
229
+
230
+ this.receivingAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (elapsedInSeconds);
231
+ this.receivingVideoBitrate = (this.totalReceivedVideoBytes * 8) / (elapsedInSeconds);
232
+ this.sendingAudioBitrate = (this.deltaSentAudioBytes * 8) / (elapsedInSeconds);
233
+ this.sendingVideoBitrate = (this.deltaSentVideoBytes * 8) / (elapsedInSeconds);
234
+
235
+ this.calculatedScore.value = sample.score;
236
+ this.detectors.update();
237
+
238
+ // emit update
239
+ this.emit('update', {
240
+ sample,
241
+ elapsedTimeInMs: now - this.updated,
242
+ });
243
+ this.updated = now;
757
244
 
758
- if (remoteOutboundTrack) {
759
- inboundAudioTrack.remoteOutboundTrack = remoteOutboundTrack;
760
-
761
- inboundAudioTrack.once('close', () => {
762
- remoteOutboundTrack?.remoteInboundTracks.delete(inboundAudioTrack.trackId ?? '');
763
- });
764
- remoteOutboundTrack.remoteInboundTracks.set(inboundAudioTrack.trackId ?? '', inboundAudioTrack);
245
+ // if result changed after update
246
+ if (this.calculatedScore.value) {
247
+ this.totalScoreSum += this.calculatedScore.value;
248
+ ++this.numberOfScoreMeasurements;
249
+ }
250
+ }
251
+
252
+ private _processClientEvent(event: ClientEvent) {
253
+ // eslint-disable-next-line no-console
254
+ // console.warn('ClientEvent', event);
255
+ switch (event.type) {
256
+ case ClientEventTypes.CLIENT_JOINED: {
257
+ if (event.timestamp) {
258
+ if (!this.joinedAt) {
259
+ this.joinedAt = event.timestamp;
260
+ this.emit('joined');
261
+ } else if (this.joinedAt < event.timestamp) {
262
+ this.emit('rejoined', event.timestamp);
263
+ } else {
264
+ this.joinedAt = event.timestamp;
265
+ logger.warn(`Client ${this.clientId} joinedAt timestamp was updated to ${event.timestamp}. the joined event will not be emitted.`);
765
266
  }
766
267
  }
767
- } catch (err) {
768
- logger.error(`Error creating inbound audio track: ${(err as Error)?.message}`);
268
+ break;
769
269
  }
770
- }
771
-
772
- for (const track of sample.inboundVideoTracks ?? []) {
773
- if (!track.peerConnectionId || !track.trackId) {
774
- logger.warn(`InboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
775
-
776
- continue;
777
- }
778
- if (isValidUuid(track.trackId) === false) {
779
- // mediasoup-probator trackId is not a valid UUID, no need to warn about it
780
- if (track.trackId === 'probator') continue;
781
- logger.warn(`InboundVideoTrack with invalid trackId: ${track.trackId}`);
782
- continue;
783
- }
784
-
785
- try {
786
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
787
-
788
- if (!peerConnection) continue;
789
-
790
- const inboundVideoTrack = peerConnection.inboundVideoTracks.get(track.trackId) ?? peerConnection.createInboundVideoTrack({
791
- trackId: track.trackId,
792
- sfuStreamId: track.sfuStreamId,
793
- sfuSinkId: track.sfuSinkId,
794
- });
795
-
796
- inboundVideoTrack.update(track, sample.timestamp);
797
-
798
- if (!inboundVideoTrack.remoteOutboundTrack) {
799
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundVideoTrack.get(inboundVideoTrack.sfuStreamId ?? '');
800
-
801
- if (remoteOutboundTrack) {
802
- inboundVideoTrack.remoteOutboundTrack = remoteOutboundTrack;
803
-
804
- inboundVideoTrack.once('close', () => {
805
- remoteOutboundTrack?.remoteInboundTracks.delete(inboundVideoTrack.trackId ?? '');
806
- });
807
- remoteOutboundTrack.remoteInboundTracks.set(inboundVideoTrack.trackId ?? '', inboundVideoTrack);
270
+ case ClientEventTypes.CLIENT_LEFT: {
271
+ if (event.timestamp) {
272
+ if (!this.leftAt) {
273
+ this.leftAt = event.timestamp;
274
+ this.emit('left');
275
+ } else {
276
+ logger.warn(`Client ${this.clientId} leftAt timestamp was already set`);
808
277
  }
809
278
  }
810
- } catch (err) {
811
- logger.error(`Error creating inbound video track: ${(err as Error)?.message}`);
812
- }
813
- }
814
-
815
- for (const track of sample.outboundAudioTracks ?? []) {
816
- if (!track.peerConnectionId || !track.trackId) {
817
- logger.warn(`OutboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
818
-
819
- continue;
820
- }
821
-
822
- try {
823
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
824
-
825
- if (!peerConnection) continue;
826
-
827
- const outboundAudioTrack = peerConnection.outboundAudioTracks.get(track.trackId) ?? peerConnection.createOutboundAudioTrack({
828
- trackId: track.trackId,
829
- sfuStreamId: track.sfuStreamId,
830
- });
831
-
832
- outboundAudioTrack.update(track, sample.timestamp);
833
-
834
- if (outboundAudioTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundAudioTrack.has(outboundAudioTrack.sfuStreamId)) {
835
- this.call.sfuStreamIdToOutboundAudioTrack.set(outboundAudioTrack.sfuStreamId, outboundAudioTrack);
836
- }
837
-
838
- } catch (err) {
839
- logger.error(`Error creating outbound audio track: ${(err as Error)?.message}`);
279
+ break;
840
280
  }
841
-
842
281
  }
843
282
 
844
- for (const track of sample.outboundVideoTracks ?? []) {
845
- if (!track.peerConnectionId || !track.trackId) {
846
- logger.warn(`OutboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
847
-
848
- continue;
849
- }
850
-
851
- try {
852
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
853
-
854
- if (!peerConnection) continue;
855
-
856
- const outboundVideoTrack = peerConnection.outboundVideoTracks.get(track.trackId) ?? peerConnection.createOutboundVideoTrack({
857
- trackId: track.trackId,
858
- sfuStreamId: track.sfuStreamId,
859
- });
860
-
861
- outboundVideoTrack.update(track, sample.timestamp);
862
-
863
- if (outboundVideoTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundVideoTrack.has(outboundVideoTrack.sfuStreamId)) {
864
- this.call.sfuStreamIdToOutboundVideoTrack.set(outboundVideoTrack.sfuStreamId, outboundVideoTrack);
865
- }
866
-
867
- } catch (err) {
868
- logger.error(`Error creating outbound video track: ${(err as Error)?.message}`);
869
- }
870
- }
283
+ this.call.observer.emit('client-event', this, event);
284
+ }
871
285
 
872
- for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
873
- if (!iceLocalCandidate.peerConnectionId) {
874
- logger.warn(`Local ice candidate without peerConnectionId: ${JSON.stringify(iceLocalCandidate)}`);
875
- continue;
286
+ public addMetadata(metadata: ClientMetaData) {
287
+ if (this.closed) return;
288
+
289
+ switch (metadata.type) {
290
+ case ClientMetaTypes.BROWSER: {
291
+ this.browser = parseJsonAs(metadata.payload);
292
+ break;
876
293
  }
877
- const peerConnection = this._peerConnections.get(iceLocalCandidate.peerConnectionId);
878
-
879
- if (!peerConnection) {
880
- logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
881
- continue;
294
+ case ClientMetaTypes.ENGINE: {
295
+ this.engine = parseJsonAs(metadata.payload);
296
+ break;
882
297
  }
883
-
884
- peerConnection.ICE.addLocalCandidate(iceLocalCandidate, sample.timestamp);
885
- }
886
-
887
- for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
888
- if (!iceRemoteCandidate.peerConnectionId) {
889
- logger.warn(`Remote ice candidate without peerConnectionId: ${JSON.stringify(iceRemoteCandidate)}`);
890
- continue;
298
+ case ClientMetaTypes.PLATFORM: {
299
+ this.platform = parseJsonAs(metadata.payload);
300
+ break;
891
301
  }
892
- const peerConnection = this._peerConnections.get(iceRemoteCandidate.peerConnectionId);
893
-
894
- if (!peerConnection) {
895
- logger.debug(`Peer connection ${iceRemoteCandidate.peerConnectionId} not found for ice remote candidate ${iceRemoteCandidate.id}`);
896
- continue;
302
+ case ClientMetaTypes.OPERATION_SYSTEM: {
303
+ this.operationSystem = parseJsonAs(metadata.payload);
304
+ break;
897
305
  }
898
-
899
- peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate, sample.timestamp);
900
306
  }
901
307
 
902
- for (const candidatePair of sample.iceCandidatePairs ?? []) {
903
- const peerConnection = this._peerConnections.get(candidatePair.peerConnectionId);
904
-
905
- if (!peerConnection) {
906
- logger.debug(`Peer connection ${candidatePair.peerConnectionId} not found for ice candidate pair ${candidatePair.localCandidateId}, ${candidatePair.remoteCandidateId}`);
907
-
908
- continue;
909
- }
308
+ this.call.observer.emit('client-metadata', this, metadata);
309
+ }
910
310
 
911
- peerConnection.ICE.update(candidatePair, sample.timestamp);
912
- }
311
+ public addIssue(issue: ClientIssue) {
312
+ if (this.closed) return;
913
313
 
914
- for (const dataChannel of sample.dataChannels ?? []) {
915
- if (!dataChannel.peerConnectionId || !dataChannel.dataChannelIdentifier) {
916
- logger.warn(`DataChannel without peerConnectionId or dataChannelIdentifier: ${JSON.stringify(dataChannel)}`);
314
+ ++this.deltaNumberOfIssues;
315
+ this.emit('issue', issue);
316
+ this.call.observer.emit('client-issue', this, issue);
317
+ }
917
318
 
918
- continue;
919
- }
319
+ public addExtensionStats(stats: ExtensionStat) {
320
+ this.call.observer.emit('client-extension-stats', this, stats);
321
+ }
920
322
 
921
- try {
922
- const peerConnection = this._peerConnections.get(dataChannel.peerConnectionId) ?? this._createPeerConnection(dataChannel.peerConnectionId);
323
+ private _updatePeerConnection(sample: PeerConnectionSample) {
324
+ let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId);
923
325
 
924
- if (!peerConnection) continue;
925
-
926
- const observedDataChannel = peerConnection.dataChannels.get(dataChannel.dataChannelIdentifier) ?? peerConnection.createDataChannel(dataChannel.dataChannelIdentifier);
927
-
928
- observedDataChannel.update(dataChannel, sample.timestamp);
929
- } catch (err) {
930
- logger.error(`Error creating data channel: ${(err as Error)?.message}`);
326
+ if (!observedPeerConnection) {
327
+ if (!sample.peerConnectionId) {
328
+ return logger.warn(
329
+ `ObservedClient received an invalid PeerConnectionSample (missing peerConnectionId field). ClientId: ${this.clientId}, CallId: ${this.call.callId}`,
330
+ sample
331
+ );
931
332
  }
932
- }
933
-
934
- // try to set the timestamps of the peer connections
935
- if (0 < this.ωpendingPeerConnectionTimestamps.length) {
936
- const newPendingTimestamps: PendingPeerConnectionTimestamp[] = [];
937
-
938
- for (const pendingTimestamp of this.ωpendingPeerConnectionTimestamps) {
939
- const peerConnection = this._peerConnections.get(pendingTimestamp.peerConnectionId);
940
333
 
941
- if (!peerConnection) {
942
- newPendingTimestamps.push(pendingTimestamp);
943
- continue;
944
- }
945
- if (pendingTimestamp.type === 'opened') peerConnection.opened = pendingTimestamp.timestamp;
946
- else if (pendingTimestamp.type === 'closed') peerConnection.closedTimestamp = pendingTimestamp.timestamp;
947
- }
334
+ observedPeerConnection = new ObservedPeerConnection(sample.peerConnectionId, this);
948
335
 
949
- this.ωpendingPeerConnectionTimestamps = newPendingTimestamps;
950
- }
336
+ observedPeerConnection.once('close', () => {
337
+ this.observedPeerConnections.delete(sample.peerConnectionId);
338
+ });
339
+ this.observedPeerConnections.set(sample.peerConnectionId, observedPeerConnection);
340
+
341
+ this.emit('newpeerconnection', observedPeerConnection);
342
+ }
343
+
344
+ observedPeerConnection.accept(sample);
345
+
346
+ observedPeerConnection.currentRttInMs;
347
+
348
+ this.deltaDataChannelBytesReceived += observedPeerConnection.deltaDataChannelBytesReceived;
349
+ this.deltaDataChannelBytesSent += observedPeerConnection.deltaDataChannelBytesSent;
350
+ this.deltaDataChannelMessagesReceived += observedPeerConnection.deltaDataChannelMessagesReceived;
351
+ this.deltaDataChannelMessagesSent += observedPeerConnection.deltaDataChannelMessagesSent;
352
+ this.deltaInboundPacketsLost += observedPeerConnection.deltaInboundPacketsLost;
353
+ this.deltaInboundPacketsReceived += observedPeerConnection.deltaInboundPacketsReceived;
354
+ this.deltaOutboundPacketsSent += observedPeerConnection.deltaOutboundPacketsSent;
355
+ this.deltaReceivedAudioBytes += observedPeerConnection.deltaReceivedAudioBytes;
356
+ this.deltaReceivedVideoBytes += observedPeerConnection.deltaReceivedVideoBytes;
357
+ this.deltaSentAudioBytes += observedPeerConnection.deltaSentAudioBytes;
358
+ this.deltaSentVideoBytes += observedPeerConnection.deltaSentVideoBytes;
359
+ this.deltaTransportReceivedBytes += observedPeerConnection.deltaTransportReceivedBytes;
360
+ this.deltaTransportSentBytes += observedPeerConnection.deltaTransportSentBytes;
361
+
362
+ this.availableIncomingBitrate += observedPeerConnection.availableIncomingBitrate;
363
+ this.availableOutgoingBitrate += observedPeerConnection.availableOutgoingBitrate;
364
+
365
+ this.numberOfDataChannels += observedPeerConnection.observedDataChannels.size;
366
+ this.numberOfInbundTracks += observedPeerConnection.observedInboundTracks.size;
367
+ this.numberOfOutboundRtpStreams += observedPeerConnection.observedOutboundRtps.size;
368
+ this.numberOfOutboundTracks += observedPeerConnection.observedOutboundTracks.size;
369
+ this.numberOfInboundRtpStreams += observedPeerConnection.observedInboundRtps.size;
370
+
371
+ if (observedPeerConnection.currentRttInMs) {
372
+ this.deltaRttInMsMeasurements.push(observedPeerConnection.currentRttInMs);
373
+ }
374
+ }
375
+
376
+ public resetSummaryMetrics() {
377
+ this.totalDataChannelBytesReceived = 0;
378
+ this.totalDataChannelBytesSent = 0;
379
+ this.totalDataChannelMessagesReceived = 0;
380
+ this.totalDataChannelMessagesSent = 0;
381
+ this.totalInboundPacketsLost = 0;
382
+ this.totalInboundPacketsReceived = 0;
383
+ this.totalOutboundPacketsSent = 0;
384
+ this.totalReceivedAudioBytes = 0;
385
+ this.totalReceivedVideoBytes = 0;
386
+ this.totalSentAudioBytes = 0;
387
+ this.totalSentVideoBytes = 0;
388
+ this.totalSentBytes = 0;
389
+ this.totalReceivedBytes = 0;
951
390
 
952
- // try to set the timestamps of the media tracks
953
- if (0 < this.ωpendingMediaTrackTimestamps.length) {
954
- const newPendingTimestamps: PendingMediaTrackTimestamp[] = [];
955
-
956
- for (const pendingTimestamp of this.ωpendingMediaTrackTimestamps) {
957
- const peerConnection = this._peerConnections.get(pendingTimestamp.peerConnectionId);
958
- const mediaTrack = peerConnection?.inboundAudioTracks.get(pendingTimestamp.mediaTrackId) ??
959
- peerConnection?.inboundVideoTracks.get(pendingTimestamp.mediaTrackId) ??
960
- peerConnection?.outboundAudioTracks.get(pendingTimestamp.mediaTrackId) ??
961
- peerConnection?.outboundVideoTracks.get(pendingTimestamp.mediaTrackId);
962
-
963
- if (!mediaTrack) {
964
- newPendingTimestamps.push(pendingTimestamp);
965
- continue;
966
- }
967
-
968
- if (pendingTimestamp.type === 'added') mediaTrack.added = pendingTimestamp.timestamp;
969
- else if (pendingTimestamp.type === 'removed') mediaTrack.removed = pendingTimestamp.timestamp;
970
- }
971
-
972
- this.ωpendingMediaTrackTimestamps = newPendingTimestamps;
973
- }
974
-
975
- // close resources that are not visited
976
- for (const peerConnection of this._peerConnections.values()) {
977
- if (!peerConnection.visited) {
978
- peerConnection.close();
979
-
980
- continue;
981
- }
982
-
983
- for (const track of peerConnection.inboundAudioTracks.values()) {
984
- if (!track.visited) {
985
- track.close();
986
-
987
- continue;
988
- }
989
-
990
- track.visited = false;
991
- }
992
- for (const track of peerConnection.inboundVideoTracks.values()) {
993
- if (!track.visited) {
994
- track.close();
995
-
996
- continue;
997
- }
998
-
999
- track.visited = false;
1000
- }
1001
- for (const track of peerConnection.outboundAudioTracks.values()) {
1002
- if (!track.visited) {
1003
- track.close();
1004
-
1005
- continue;
1006
- }
1007
-
1008
- track.visited = false;
1009
- }
1010
- for (const dataChannel of peerConnection.dataChannels.values()) {
1011
- if (!dataChannel.visited) {
1012
- dataChannel.close();
1013
-
1014
- continue;
1015
- }
1016
-
1017
- dataChannel.visited = false;
1018
- }
1019
-
1020
- peerConnection.visited = false;
1021
- }
1022
-
1023
- // update metrics
1024
- const wasUsingTURN = this.usingTURN;
1025
- const elapsedTimeInMs = now - this.updated;
1026
-
1027
- this.usingTURN = false;
1028
- this.availableIncomingBitrate = 0;
1029
- this.availableOutgoingBitrate = 0;
1030
- this.deltaInboundPacketsLost = 0;
1031
- this.deltaInboundPacketsReceived = 0;
1032
- this.deltaOutboundPacketsSent = 0;
1033
- this.deltaReceivedAudioBytes = 0;
1034
- this.deltaReceivedVideoBytes = 0;
1035
- this.deltaSentAudioBytes = 0;
1036
- this.deltaSentVideoBytes = 0;
1037
- this.deltaDataChannelBytesReceived = 0;
1038
- this.deltaDataChannelBytesSent = 0;
391
+ this.numberOfIssues = 0;
1039
392
 
1040
- this.outboundAudioBitrate = 0;
1041
- this.outboundVideoBitrate = 0;
1042
- this.inboundAudioBitrate = 0;
1043
- this.inboundVideoBitrate = 0;
1044
- let sumRttInMs = 0;
1045
- let anyPeerConnectionUsingTurn = false;
1046
-
1047
- let minPcScore: number | undefined;
1048
- let maxPcScore: number | undefined;
1049
- let numberOfScoredPeerConnections = 0;
1050
-
1051
- for (const peerConnection of this._peerConnections.values()) {
1052
- if (peerConnection.closed) continue;
1053
-
1054
- peerConnection.updateMetrics();
393
+ this.totalScoreSum = 0;
394
+ this.numberOfScoreMeasurements = 0;
395
+ }
396
+
397
+ public createSummary(): ObservedClientSummary {
398
+ return {
399
+ totalRttLt50Measurements: this.totalRttLt50Measurements,
400
+ totalRttLt150Measurements: this.totalRttLt150Measurements,
401
+ totalRttLt300Measurements: this.totalRttLt300Measurements,
402
+ totalRttGtOrEq300Measurements: this.totalRttGtOrEq300Measurements,
403
+ totalDataChannelBytesReceived: this.totalDataChannelBytesReceived,
404
+ totalDataChannelBytesSent: this.totalDataChannelBytesSent,
405
+ totalDataChannelMessagesReceived: this.totalDataChannelMessagesReceived,
406
+ totalDataChannelMessagesSent: this.totalDataChannelMessagesSent,
407
+ totalInboundPacketsLost: this.totalInboundPacketsLost,
408
+ totalInboundPacketsReceived: this.totalInboundPacketsReceived,
409
+ totalOutboundPacketsSent: this.totalOutboundPacketsSent,
410
+ totalReceivedAudioBytes: this.totalReceivedAudioBytes,
411
+ totalReceivedVideoBytes: this.totalReceivedVideoBytes,
412
+ totalSentAudioBytes: this.totalSentAudioBytes,
413
+ totalSentVideoBytes: this.totalSentVideoBytes,
414
+ totalSentBytes: this.totalSentBytes,
415
+ totalReceivedBytes: this.totalReceivedBytes,
1055
416
 
1056
- this.deltaInboundPacketsLost += peerConnection.deltaInboundPacketsLost;
1057
- this.deltaInboundPacketsReceived += peerConnection.deltaInboundPacketsReceived;
1058
- this.deltaOutboundPacketsSent += peerConnection.deltaOutboundPacketsSent;
1059
- this.deltaReceivedAudioBytes += peerConnection.deltaReceivedAudioBytes;
1060
- this.deltaReceivedVideoBytes += peerConnection.deltaReceivedVideoBytes;
1061
- this.deltaSentAudioBytes += peerConnection.deltaSentAudioBytes;
1062
- this.deltaSentVideoBytes += peerConnection.deltaSentVideoBytes;
1063
- this.deltaDataChannelBytesReceived += peerConnection.deltaDataChannelBytesReceived;
1064
- this.deltaDataChannelBytesSent += peerConnection.deltaDataChannelBytesSent;
417
+ numberOfIssues: this.numberOfIssues,
1065
418
 
1066
- this.availableIncomingBitrate += peerConnection.availableIncomingBitrate ?? 0;
1067
- this.availableOutgoingBitrate += peerConnection.availableOutgoingBitrate ?? 0;
1068
-
1069
- sumRttInMs += peerConnection.avgRttInMs ?? 0;
1070
-
1071
- anyPeerConnectionUsingTurn ||= peerConnection.usingTURN;
1072
-
1073
- if (peerConnection.score) {
1074
- if (minPcScore === undefined || peerConnection.score.score < minPcScore) minPcScore = peerConnection.score.score;
1075
- if (maxPcScore === undefined || peerConnection.score.score > maxPcScore) maxPcScore = peerConnection.score.score;
1076
-
1077
- ++numberOfScoredPeerConnections;
1078
- }
1079
- }
1080
-
1081
- this.score = {
1082
- score: minPcScore ?? -1,
1083
- remarks: [ {
1084
- severity: 'none',
1085
- text: `Min and max score of all peer connections: ${minPcScore}, ${maxPcScore}, number of PeerConnections with scores: ${numberOfScoredPeerConnections}`,
1086
- } ],
1087
- timestamp: sample.timestamp,
419
+ totalScoreSum: this.totalScoreSum,
420
+ numberOfScoreMeasurements: this.numberOfScoreMeasurements,
1088
421
  };
1089
- this.emit('score', this.score);
1090
-
1091
- this.usingTURN = anyPeerConnectionUsingTurn === true;
1092
-
1093
- if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
1094
-
1095
- this.totalSentBytes += this.deltaSentAudioBytes + this.deltaSentVideoBytes;
1096
- this.totalReceivedBytes += this.deltaReceivedAudioBytes + this.deltaReceivedVideoBytes;
1097
- this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
1098
- this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
1099
- this.totalSentAudioBytes += this.deltaSentAudioBytes;
1100
- this.totalSentVideoBytes += this.deltaSentVideoBytes;
1101
- this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
1102
- this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
1103
- this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
1104
- this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
1105
- this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
1106
-
1107
- this.outboundAudioBitrate = (this.deltaSentAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1108
- this.outboundVideoBitrate = (this.deltaSentVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1109
- this.inboundAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1110
- this.inboundVideoBitrate = (this.deltaReceivedVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1111
-
1112
- this.avgRttInMs = this._peerConnections.size ? sumRttInMs / this._peerConnections.size : undefined;
1113
-
1114
- // to make sure when sample is emitted it can be associated to this client
1115
- sample.clientId = this.clientId;
1116
-
1117
- this.updated = now;
1118
- this.lastStatsTimestamp = sample.timestamp;
1119
- this.emit('update', {
1120
- sample,
1121
- elapsedTimeInMs,
1122
- });
1123
422
  }
1124
423
 
1125
- private _addAndEmitIssue(issue: ClientIssue) {
1126
- this.issues.push(issue);
1127
-
1128
- if (issue.peerConnectionId) {
1129
- const peerConnection = this._peerConnections.get(issue.peerConnectionId);
1130
-
1131
- if (peerConnection) {
1132
-
1133
- if (issue.trackId) {
1134
- const track = peerConnection.inboundAudioTracks.get(issue.trackId) ?? peerConnection.inboundVideoTracks.get(issue.trackId);
424
+ private _updateRttMeasurements() {
425
+ if (this.deltaRttInMsMeasurements.length < 1) {
426
+ this.currentRttInMs = undefined;
1135
427
 
1136
- if (track) track.ωpendingIssuesForScores.push(issue);
1137
- } else {
1138
- peerConnection.ωpendingIssuesForScores.push(issue);
1139
- }
1140
- }
428
+ return;
1141
429
  }
430
+ this.currentRttInMs = getMedian(this.deltaRttInMsMeasurements, false);
1142
431
 
1143
- this.emit('issue', issue);
1144
- }
1145
-
1146
- private _createPeerConnection(peerConnectionId: string, label?: string): ObservedPeerConnection {
1147
- const result = new ObservedPeerConnection({
1148
- peerConnectionId,
1149
- label,
1150
- }, this);
432
+ if (this.currentRttInMs < 0) {
433
+ logger.warn(`Current Rtt for client ${this.clientId} is smaller than 0`);
434
+ this.currentRttInMs = undefined;
1151
435
 
1152
- const onNewSelectedCandidatePair = ({ localCandidate, remoteCandidate }: { localCandidate: IceLocalCandidate, remoteCandidate: IceRemoteCandidate }) => {
1153
- this.emit('selectedcandidatepair', {
1154
- peerConnection: result,
1155
- localCandidate,
1156
- remoteCandidate,
1157
- });
1158
- };
1159
-
1160
- result.once('close', () => {
1161
- result.ICE.off('new-selected-candidate-pair', onNewSelectedCandidatePair);
1162
- this._peerConnections.delete(peerConnectionId);
1163
- });
1164
- result.ICE.on('new-selected-candidate-pair', onNewSelectedCandidatePair);
1165
- this._peerConnections.set(peerConnectionId, result);
1166
-
1167
- this.emit('newpeerconnection', result);
1168
-
1169
- return result;
436
+ return;
437
+ }
438
+
439
+ if (this.currentRttInMs < 50) {
440
+ this.deltaRttLt50Measurements += 1;
441
+ } else if (this.currentRttInMs < 150) {
442
+ this.deltaRttLt150Measurements += 1;
443
+ } else if (this.currentRttInMs < 300) {
444
+ this.deltaRttLt300Measurements += 1;
445
+ } else if (300 <= this.currentRttInMs) {
446
+ this.deltaRttGtOrEq300Measurements += 1;
447
+ }
1170
448
  }
1171
- }
449
+ }