@observertc/observer-js 0.42.9 → 1.0.0-5f150eb.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 (303) hide show
  1. package/.prettierrc +6 -6
  2. package/README.md +493 -63
  3. package/lib/ObservedCall.d.ts +48 -43
  4. package/lib/ObservedCall.d.ts.map +1 -1
  5. package/lib/ObservedCall.js +164 -107
  6. package/lib/ObservedCallEventMonitor.d.ts +107 -0
  7. package/lib/ObservedCallEventMonitor.d.ts.map +1 -0
  8. package/lib/ObservedCallEventMonitor.js +321 -0
  9. package/lib/ObservedCallSummary.d.ts +18 -0
  10. package/lib/ObservedCallSummary.d.ts.map +1 -0
  11. package/lib/ObservedCertificate.d.ts +19 -0
  12. package/lib/ObservedCertificate.d.ts.map +1 -0
  13. package/lib/ObservedCertificate.js +38 -0
  14. package/lib/ObservedClient.d.ts +78 -145
  15. package/lib/ObservedClient.d.ts.map +1 -1
  16. package/lib/ObservedClient.js +661 -746
  17. package/lib/ObservedClientEventMonitor.d.ts +91 -0
  18. package/lib/ObservedClientEventMonitor.d.ts.map +1 -0
  19. package/lib/ObservedClientEventMonitor.js +254 -0
  20. package/lib/ObservedClientSummary.d.ts +23 -0
  21. package/lib/ObservedClientSummary.d.ts.map +1 -0
  22. package/lib/ObservedClientSummary.js +2 -0
  23. package/lib/ObservedCodec.d.ts +22 -0
  24. package/lib/ObservedCodec.d.ts.map +1 -0
  25. package/lib/ObservedCodec.js +45 -0
  26. package/lib/ObservedDataChannel.d.ts +25 -69
  27. package/lib/ObservedDataChannel.d.ts.map +1 -1
  28. package/lib/ObservedDataChannel.js +69 -109
  29. package/lib/ObservedIceCandidate.d.ts +29 -0
  30. package/lib/ObservedIceCandidate.d.ts.map +1 -0
  31. package/lib/ObservedIceCandidate.js +59 -0
  32. package/lib/ObservedIceCandidatePair.d.ts +45 -0
  33. package/lib/ObservedIceCandidatePair.d.ts.map +1 -0
  34. package/lib/ObservedIceCandidatePair.js +127 -0
  35. package/lib/ObservedIceTransport.d.ts +36 -0
  36. package/lib/ObservedIceTransport.d.ts.map +1 -0
  37. package/lib/ObservedIceTransport.js +93 -0
  38. package/lib/ObservedInboundRtp.d.ts +92 -0
  39. package/lib/ObservedInboundRtp.d.ts.map +1 -0
  40. package/lib/ObservedInboundRtp.js +193 -0
  41. package/lib/ObservedInboundTrack.d.ts +34 -0
  42. package/lib/ObservedInboundTrack.d.ts.map +1 -0
  43. package/lib/ObservedInboundTrack.js +92 -0
  44. package/lib/ObservedMediaPlayout.d.ts +22 -0
  45. package/lib/ObservedMediaPlayout.d.ts.map +1 -0
  46. package/lib/ObservedMediaPlayout.js +42 -0
  47. package/lib/ObservedMediaSource.d.ts +28 -0
  48. package/lib/ObservedMediaSource.d.ts.map +1 -0
  49. package/lib/ObservedMediaSource.js +55 -0
  50. package/lib/ObservedOutboundRtp.d.ts +62 -0
  51. package/lib/ObservedOutboundRtp.d.ts.map +1 -0
  52. package/lib/ObservedOutboundRtp.js +144 -0
  53. package/lib/ObservedOutboundTrack.d.ts +34 -0
  54. package/lib/ObservedOutboundTrack.d.ts.map +1 -0
  55. package/lib/ObservedOutboundTrack.js +65 -0
  56. package/lib/ObservedPeerConnection.d.ts +154 -80
  57. package/lib/ObservedPeerConnection.d.ts.map +1 -1
  58. package/lib/ObservedPeerConnection.js +724 -357
  59. package/lib/ObservedPeerConnectionTransport.d.ts +17 -0
  60. package/lib/ObservedPeerConnectionTransport.d.ts.map +1 -0
  61. package/lib/ObservedPeerConnectionTransport.js +34 -0
  62. package/lib/ObservedRemoteInboundRtp.d.ts +30 -0
  63. package/lib/ObservedRemoteInboundRtp.d.ts.map +1 -0
  64. package/lib/ObservedRemoteInboundRtp.js +60 -0
  65. package/lib/ObservedRemoteOutboundRtp.d.ts +30 -0
  66. package/lib/ObservedRemoteOutboundRtp.d.ts.map +1 -0
  67. package/lib/ObservedRemoteOutboundRtp.js +60 -0
  68. package/lib/ObservedTURN.d.ts +31 -0
  69. package/lib/ObservedTURN.d.ts.map +1 -0
  70. package/lib/ObservedTURN.js +58 -0
  71. package/lib/ObservedTurnServer.d.ts +25 -0
  72. package/lib/ObservedTurnServer.d.ts.map +1 -0
  73. package/lib/ObservedTurnServer.js +59 -0
  74. package/lib/Observer.d.ts +46 -54
  75. package/lib/Observer.d.ts.map +1 -1
  76. package/lib/Observer.js +131 -226
  77. package/lib/ObserverEventMonitor.d.ts +141 -0
  78. package/lib/ObserverEventMonitor.d.ts.map +1 -0
  79. package/lib/ObserverEventMonitor.js +440 -0
  80. package/lib/ObserverSummary.d.ts +13 -0
  81. package/lib/ObserverSummary.d.ts.map +1 -0
  82. package/lib/ObserverSummary.js +2 -0
  83. package/lib/Reports.d.ts +85 -0
  84. package/lib/Reports.d.ts.map +1 -0
  85. package/lib/Reports.js +2 -0
  86. package/lib/common/Middleware.d.ts +10 -1
  87. package/lib/common/Middleware.d.ts.map +1 -1
  88. package/lib/common/Middleware.js +55 -36
  89. package/lib/common/logger.d.ts +8 -4
  90. package/lib/common/logger.d.ts.map +1 -1
  91. package/lib/common/logger.js +44 -68
  92. package/lib/common/types.d.ts +0 -21
  93. package/lib/common/types.d.ts.map +1 -1
  94. package/lib/common/types.js +1 -0
  95. package/lib/common/utils.d.ts +3 -1
  96. package/lib/common/utils.d.ts.map +1 -1
  97. package/lib/common/utils.js +23 -3
  98. package/lib/detectors/Detector.d.ts +5 -0
  99. package/lib/detectors/Detector.d.ts.map +1 -0
  100. package/lib/detectors/Detector.js +3 -0
  101. package/lib/detectors/Detectors.d.ts +11 -0
  102. package/lib/detectors/Detectors.d.ts.map +1 -0
  103. package/lib/detectors/Detectors.js +34 -0
  104. package/lib/index.d.ts +27 -25
  105. package/lib/index.d.ts.map +1 -1
  106. package/lib/index.js +45 -41
  107. package/lib/mediasoup/ObservedMediaRouter.d.ts +10 -0
  108. package/lib/mediasoup/ObservedMediaRouter.d.ts.map +1 -0
  109. package/lib/mediasoup/ObservedMediaRouter.js +6 -0
  110. package/lib/monitors/TurnUsageMonitor.d.ts +0 -52
  111. package/lib/monitors/TurnUsageMonitor.d.ts.map +1 -1
  112. package/lib/monitors/TurnUsageMonitor.js +145 -120
  113. package/lib/schema/ClientEventTypes.d.ts +228 -0
  114. package/lib/schema/ClientEventTypes.d.ts.map +1 -0
  115. package/lib/schema/ClientEventTypes.js +41 -0
  116. package/lib/schema/ClientMetaTypes.d.ts +34 -0
  117. package/lib/schema/ClientMetaTypes.d.ts.map +1 -0
  118. package/lib/schema/ClientMetaTypes.js +16 -0
  119. package/lib/schema/ClientSample.d.ts +1333 -0
  120. package/lib/schema/ClientSample.d.ts.map +1 -0
  121. package/lib/schema/ClientSample.js +4 -0
  122. package/lib/scores/CalculatedScore.d.ts +6 -0
  123. package/lib/scores/CalculatedScore.d.ts.map +1 -0
  124. package/lib/scores/CalculatedScore.js +2 -0
  125. package/lib/scores/DefaultCallScoreCalculator.d.ts +7 -0
  126. package/lib/scores/DefaultCallScoreCalculator.d.ts.map +1 -0
  127. package/lib/scores/DefaultCallScoreCalculator.js +21 -0
  128. package/lib/scores/ScoreCalculator.d.ts +4 -0
  129. package/lib/scores/ScoreCalculator.d.ts.map +1 -0
  130. package/lib/scores/ScoreCalculator.js +2 -0
  131. package/lib/updaters/CallUpdater.d.ts +5 -0
  132. package/lib/updaters/CallUpdater.d.ts.map +1 -0
  133. package/lib/updaters/CallUpdater.js +2 -0
  134. package/lib/updaters/ObserverUpdater.d.ts +5 -0
  135. package/lib/updaters/ObserverUpdater.d.ts.map +1 -0
  136. package/lib/updaters/ObserverUpdater.js +2 -0
  137. package/lib/updaters/OnAllCallObserverUpdater.d.ts +15 -0
  138. package/lib/updaters/OnAllCallObserverUpdater.d.ts.map +1 -0
  139. package/lib/updaters/OnAllCallObserverUpdater.js +50 -0
  140. package/lib/updaters/OnAllClientCallUpdater.d.ts +15 -0
  141. package/lib/updaters/OnAllClientCallUpdater.d.ts.map +1 -0
  142. package/lib/updaters/OnAllClientCallUpdater.js +53 -0
  143. package/lib/updaters/OnAnyCallObserverUpdater.d.ts +12 -0
  144. package/lib/updaters/OnAnyCallObserverUpdater.d.ts.map +1 -0
  145. package/lib/updaters/OnAnyCallObserverUpdater.js +37 -0
  146. package/lib/updaters/OnAnyClientCallUpdater.d.ts +12 -0
  147. package/lib/updaters/OnAnyClientCallUpdater.d.ts.map +1 -0
  148. package/lib/updaters/OnAnyClientCallUpdater.js +36 -0
  149. package/lib/updaters/OnIntervalUpdater.d.ts +10 -0
  150. package/lib/updaters/OnIntervalUpdater.d.ts.map +1 -0
  151. package/lib/updaters/OnIntervalUpdater.js +17 -0
  152. package/lib/updaters/Updater.d.ts +6 -0
  153. package/lib/updaters/Updater.d.ts.map +1 -0
  154. package/lib/updaters/Updater.js +2 -0
  155. package/lib/utils/MediasoupRemoteTrackResolver.d.ts +25 -0
  156. package/lib/utils/MediasoupRemoteTrackResolver.d.ts.map +1 -0
  157. package/lib/utils/MediasoupRemoteTrackResolver.js +110 -0
  158. package/lib/utils/RemoteTrackResolver.d.ts +7 -0
  159. package/lib/utils/RemoteTrackResolver.d.ts.map +1 -0
  160. package/lib/utils/RemoteTrackResolver.js +2 -0
  161. package/package.json +4 -4
  162. package/src/ObservedCall.ts +243 -161
  163. package/src/ObservedCallEventMonitor.ts +402 -0
  164. package/src/ObservedCallSummary.ts +22 -0
  165. package/src/ObservedCertificate.ts +43 -0
  166. package/src/ObservedClient.ts +715 -1003
  167. package/src/ObservedClientEventMonitor.ts +309 -0
  168. package/src/ObservedClientSummary.ts +24 -0
  169. package/src/ObservedCodec.ts +49 -0
  170. package/src/ObservedDataChannel.ts +71 -181
  171. package/src/ObservedIceCandidate.ts +64 -0
  172. package/src/ObservedIceCandidatePair.ts +134 -0
  173. package/src/ObservedIceTransport.ts +99 -0
  174. package/src/ObservedInboundRtp.ts +205 -0
  175. package/src/ObservedInboundTrack.ts +105 -0
  176. package/src/ObservedMediaPlayout.ts +49 -0
  177. package/src/ObservedMediaSource.ts +60 -0
  178. package/src/ObservedOutboundRtp.ts +156 -0
  179. package/src/ObservedOutboundTrack.ts +82 -0
  180. package/src/ObservedPeerConnection.ts +933 -402
  181. package/src/ObservedPeerConnectionTransport.ts +38 -0
  182. package/src/ObservedRemoteInboundRtp.ts +64 -0
  183. package/src/ObservedRemoteOutboundRtp.ts +65 -0
  184. package/src/ObservedTURN.ts +86 -0
  185. package/src/ObservedTurnServer.ts +72 -0
  186. package/src/Observer.ts +183 -298
  187. package/src/ObserverEventMonitor.ts +561 -0
  188. package/src/ObserverSummary.ts +15 -0
  189. package/src/Reports.ts +87 -0
  190. package/src/common/Middleware.ts +70 -42
  191. package/src/common/logger.ts +60 -69
  192. package/src/common/types.ts +0 -42
  193. package/src/common/utils.ts +24 -2
  194. package/src/detectors/Detector.ts +6 -0
  195. package/src/detectors/Detectors.ts +38 -0
  196. package/src/index.ts +30 -91
  197. package/src/mediasoup/ObservedMediaRouter.ts +10 -0
  198. package/src/monitors/TurnUsageMonitor.ts +164 -164
  199. package/src/schema/ClientEventTypes.ts +280 -0
  200. package/src/schema/ClientMetaTypes.ts +41 -0
  201. package/src/schema/ClientSample.ts +1682 -0
  202. package/src/scores/CalculatedScore.ts +6 -0
  203. package/src/scores/DefaultCallScoreCalculator.ts +23 -0
  204. package/src/scores/ScoreCalculator.ts +3 -0
  205. package/src/updaters/CallUpdater.ts +5 -0
  206. package/src/updaters/ObserverUpdater.ts +5 -0
  207. package/src/updaters/OnAllCallObserverUpdater.ts +59 -0
  208. package/src/updaters/OnAllClientCallUpdater.ts +61 -0
  209. package/src/updaters/OnAnyCallObserverUpdater.ts +41 -0
  210. package/src/updaters/OnAnyClientCallUpdater.ts +39 -0
  211. package/src/updaters/OnIntervalUpdater.ts +19 -0
  212. package/src/updaters/Updater.ts +5 -0
  213. package/src/utils/MediasoupRemoteTrackResolver.ts +155 -0
  214. package/src/utils/RemoteTrackResolver.ts +12 -0
  215. package/tsconfig.json +3 -3
  216. package/lib/ObservedICE.d.ts +0 -64
  217. package/lib/ObservedICE.d.ts.map +0 -1
  218. package/lib/ObservedICE.js +0 -141
  219. package/lib/ObservedInboundAudioTrack.d.ts +0 -96
  220. package/lib/ObservedInboundAudioTrack.d.ts.map +0 -1
  221. package/lib/ObservedInboundAudioTrack.js +0 -217
  222. package/lib/ObservedInboundVideoTrack.d.ts +0 -101
  223. package/lib/ObservedInboundVideoTrack.d.ts.map +0 -1
  224. package/lib/ObservedInboundVideoTrack.js +0 -232
  225. package/lib/ObservedOutboundAudioTrack.d.ts +0 -87
  226. package/lib/ObservedOutboundAudioTrack.d.ts.map +0 -1
  227. package/lib/ObservedOutboundAudioTrack.js +0 -186
  228. package/lib/ObservedOutboundVideoTrack.d.ts +0 -91
  229. package/lib/ObservedOutboundVideoTrack.d.ts.map +0 -1
  230. package/lib/ObservedOutboundVideoTrack.js +0 -193
  231. package/lib/ObservedSfu.d.ts +0 -59
  232. package/lib/ObservedSfu.d.ts.map +0 -1
  233. package/lib/ObservedSfu.js +0 -157
  234. package/lib/ObservedSfuInboundRtpPad.d.ts +0 -42
  235. package/lib/ObservedSfuInboundRtpPad.d.ts.map +0 -1
  236. package/lib/ObservedSfuInboundRtpPad.js +0 -71
  237. package/lib/ObservedSfuOutboundRtpPad.d.ts +0 -42
  238. package/lib/ObservedSfuOutboundRtpPad.d.ts.map +0 -1
  239. package/lib/ObservedSfuOutboundRtpPad.js +0 -72
  240. package/lib/ObservedSfuSctpChannel.d.ts +0 -42
  241. package/lib/ObservedSfuSctpChannel.d.ts.map +0 -1
  242. package/lib/ObservedSfuSctpChannel.js +0 -57
  243. package/lib/ObservedSfuTransport.d.ts +0 -58
  244. package/lib/ObservedSfuTransport.d.ts.map +0 -1
  245. package/lib/ObservedSfuTransport.js +0 -120
  246. package/lib/RemoteTrackAssigner.d.ts +0 -14
  247. package/lib/RemoteTrackAssigner.d.ts.map +0 -1
  248. package/lib/RemoteTrackAssigner.js +0 -52
  249. package/lib/ReportsCollector.d.ts +0 -109
  250. package/lib/ReportsCollector.d.ts.map +0 -1
  251. package/lib/ReportsCollector.js +0 -207
  252. package/lib/common/CalculatedScore.d.ts +0 -80
  253. package/lib/common/CalculatedScore.d.ts.map +0 -1
  254. package/lib/common/CalculatedScore.js +0 -162
  255. package/lib/common/CallEventType.d.ts +0 -63
  256. package/lib/common/CallEventType.d.ts.map +0 -1
  257. package/lib/common/CallEventType.js +0 -39
  258. package/lib/common/CallMetaReports.d.ts +0 -42
  259. package/lib/common/CallMetaReports.d.ts.map +0 -1
  260. package/lib/common/CallMetaReports.js +0 -36
  261. package/lib/common/QualityScoreUtils.d.ts +0 -1
  262. package/lib/common/QualityScoreUtils.d.ts.map +0 -1
  263. package/lib/common/QualityScoreUtils.js +0 -1
  264. package/lib/common/TypedEventEmitter.d.ts +0 -22
  265. package/lib/common/TypedEventEmitter.d.ts.map +0 -1
  266. package/lib/common/TypedEventEmitter.js +0 -11
  267. package/lib/common/callEventReports.d.ts +0 -10
  268. package/lib/common/callEventReports.d.ts.map +0 -1
  269. package/lib/common/callEventReports.js +0 -112
  270. package/lib/monitors/CallSummary.d.ts +0 -37
  271. package/lib/monitors/CallSummary.d.ts.map +0 -1
  272. package/lib/monitors/CallSummaryMonitor.d.ts +0 -32
  273. package/lib/monitors/CallSummaryMonitor.d.ts.map +0 -1
  274. package/lib/monitors/CallSummaryMonitor.js +0 -152
  275. package/lib/monitors/ClientIssueMonitor.d.ts +0 -32
  276. package/lib/monitors/ClientIssueMonitor.d.ts.map +0 -1
  277. package/lib/monitors/ClientIssueMonitor.js +0 -49
  278. package/lib/monitors/SfuServerMonitor.d.ts +0 -79
  279. package/lib/monitors/SfuServerMonitor.d.ts.map +0 -1
  280. package/lib/monitors/SfuServerMonitor.js +0 -198
  281. package/src/ObservedICE.ts +0 -237
  282. package/src/ObservedInboundAudioTrack.ts +0 -344
  283. package/src/ObservedInboundVideoTrack.ts +0 -361
  284. package/src/ObservedOutboundAudioTrack.ts +0 -307
  285. package/src/ObservedOutboundVideoTrack.ts +0 -319
  286. package/src/ObservedSfu.ts +0 -231
  287. package/src/ObservedSfuInboundRtpPad.ts +0 -106
  288. package/src/ObservedSfuOutboundRtpPad.ts +0 -107
  289. package/src/ObservedSfuSctpChannel.ts +0 -103
  290. package/src/ObservedSfuTransport.ts +0 -185
  291. package/src/RemoteTrackAssigner.ts +0 -68
  292. package/src/ReportsCollector.ts +0 -336
  293. package/src/common/CalculatedScore.ts +0 -188
  294. package/src/common/CallEventType.ts +0 -69
  295. package/src/common/CallMetaReports.ts +0 -82
  296. package/src/common/QualityScoreUtils.ts +0 -0
  297. package/src/common/TypedEventEmitter.ts +0 -32
  298. package/src/common/callEventReports.ts +0 -178
  299. package/src/monitors/CallSummary.ts +0 -38
  300. package/src/monitors/CallSummaryMonitor.ts +0 -192
  301. package/src/monitors/ClientIssueMonitor.ts +0 -88
  302. package/src/monitors/SfuServerMonitor.ts +0 -285
  303. /package/lib/{monitors/CallSummary.js → ObservedCallSummary.js} +0 -0
@@ -1,1171 +1,883 @@
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 { parseJsonAs } from './common/utils';
11
+ import { CalculatedScore } from './scores/CalculatedScore';
12
+ import { Detectors } from './detectors/Detectors';
13
+ import { ClientReport, TrackReport } from './Reports';
14
+ import { ObservedInboundTrack } from './ObservedInboundTrack';
15
+ import { ObservedOutboundTrack } from './ObservedOutboundTrack';
14
16
 
15
17
  const logger = createLogger('ObservedClient');
16
18
 
17
- export type ObservedClientModel= {
19
+ export type ObservedClientSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = {
18
20
  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
- },
21
+ appData?: AppData;
22
+ closeClientIfIdleForMs?: number;
30
23
  };
31
24
 
32
25
  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],
26
+ update: [sample: ClientSample, elapsedTimeInMs: number];
27
+ close: [];
28
+ joined: [];
29
+ issue: [ClientIssue];
30
+ metaData: [ClientMetaData];
31
+ rejoined: [timestamp: number];
32
+ left: [];
33
+ usingturn: [boolean];
34
+ usermediaerror: [string];
35
+ extensionStats: [ExtensionStat];
36
+ clientEvent: [ClientEvent];
37
+
38
+ newpeerconnection: [ObservedPeerConnection];
39
+
40
+ trackreport: [TrackReport];
65
41
  };
66
42
 
67
- export declare interface ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> {
43
+ export declare interface ObservedClient {
68
44
  on<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
69
45
  off<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
70
46
  once<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
71
47
  emit<U extends keyof ObservedClientEvents>(event: U, ...args: ObservedClientEvents[U]): boolean;
72
- readonly appData: AppData;
73
48
  }
74
49
 
75
- type PendingPeerConnectionTimestamp = {
76
- type: 'opened' | 'closed'
77
- peerConnectionId: string;
78
- timestamp: number;
79
- }
50
+ export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
51
+ public readonly detectors: Detectors;
80
52
 
81
- type PendingMediaTrackTimestamp = {
82
- type: 'added' | 'removed'
83
- peerConnectionId: string;
84
- mediaTrackId: string;
85
- timestamp: number;
86
- }
53
+ public readonly clientId: string;
54
+ public readonly observedPeerConnections = new Map<string, ObservedPeerConnection>();
55
+ public readonly calculatedScore: CalculatedScore = {
56
+ weight: 1,
57
+ value: undefined,
58
+ };
59
+
60
+ public readonly settings: Pick<ObservedClientSettings, 'closeClientIfIdleForMs'>;
61
+
62
+ public appData: AppData;
63
+ public attachments?: Record<string, unknown>;
87
64
 
88
- export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
89
-
90
- public readonly created = Date.now();
91
65
  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;
66
+ public acceptedSamples = 0;
67
+ public closed = false;
100
68
 
69
+ public joinedAt?: number;
70
+ public leftAt?: number;
71
+ public closedAt?: number;
72
+ public lastSampleTimestamp?: number;
101
73
  // the timestamp of the CLIENT_JOINED event
102
- private _joined?: number;
103
- private _left?: number;
104
- public lastStatsTimestamp?: number;
74
+ public operationSystem?: MetaData.OperationSystem;
75
+ public engine?: MetaData.Engine;
76
+ public platform?: MetaData.Platform;
77
+ public browser?: MetaData.Browser;
78
+ public mediaConstraints: string[] = [];
105
79
 
106
- public score?: CalculatedScore;
107
80
  public usingTURN = false;
81
+ public usingTCP = false;
108
82
  public availableOutgoingBitrate = 0;
109
83
  public availableIncomingBitrate = 0;
110
- public totalInboundPacketsLost = 0;
111
- public totalInboundPacketsReceived = 0;
112
- public totalOutboundPacketsSent = 0;
113
- public totalDataChannelBytesSent = 0;
114
- public totalDataChannelBytesReceived = 0;
115
- public totalSentBytes = 0;
116
- public totalReceivedBytes = 0;
117
- public totalReceivedAudioBytes = 0;
118
- public totalReceivedVideoBytes = 0;
119
- public totalSentAudioBytes = 0;
120
- public totalSentVideoBytes = 0;
121
84
 
85
+ // public totalInboundPacketsLost = 0;
86
+ // public totalInboundPacketsReceived = 0;
87
+ // public totalOutboundPacketsSent = 0;
88
+ // public totalDataChannelBytesSent = 0;
89
+ // public totalDataChannelBytesReceived = 0;
90
+ // public totalDataChannelMessagesSent = 0;
91
+ // public totalDataChannelMessagesReceived = 0;
92
+ // public totalReceivedAudioBytes = 0;
93
+ // public totalReceivedVideoBytes = 0;
94
+ // public totalSentAudioBytes = 0;
95
+ // public totalSentVideoBytes = 0;
96
+ // public totalSentBytes = 0;
97
+ // public totalReceivedBytes = 0;
98
+ // public totalRttLt50Measurements = 0;
99
+ // public totalRttLt150Measurements = 0;
100
+ // public totalRttLt300Measurements = 0;
101
+ // public totalRttGtOrEq300Measurements = 0;
102
+
122
103
  public deltaReceivedAudioBytes = 0;
123
104
  public deltaReceivedVideoBytes = 0;
124
105
  public deltaSentAudioBytes = 0;
125
106
  public deltaSentVideoBytes = 0;
126
107
  public deltaDataChannelBytesSent = 0;
127
108
  public deltaDataChannelBytesReceived = 0;
109
+ public deltaDataChannelMessagesSent = 0;
110
+ public deltaDataChannelMessagesReceived = 0;
128
111
  public deltaInboundPacketsLost = 0;
129
112
  public deltaInboundPacketsReceived = 0;
130
113
  public deltaOutboundPacketsSent = 0;
114
+ public deltaTransportSentBytes = 0;
115
+ public deltaTransportReceivedBytes = 0;
116
+ public deltaRttLt50Measurements = 0;
117
+ public deltaRttLt150Measurements = 0;
118
+ public deltaRttLt300Measurements = 0;
119
+ public deltaRttGtOrEq300Measurements = 0;
131
120
 
132
- public avgRttInMs?: number;
133
- public outboundAudioBitrate?: number;
134
- public outboundVideoBitrate?: number;
135
- public inboundAudioBitrate?: number;
136
- public inboundVideoBitrate?: number;
137
- public mediaConstraints: string[] = [];
121
+ public currentMaxRttInMs?: number;
122
+ public currentMinRttInMs?: number;
123
+ public currentAvgRttInMs?: number;
138
124
 
139
- public readonly mediaDevices: MediaDevice[] = [];
140
- public readonly mediaCodecs: MediaCodecStats[] = [];
141
- public readonly userMediaErrors: string[] = [];
142
- public issues: ClientIssue[] = [];
125
+ public sendingAudioBitrate = 0;
126
+ public sendingVideoBitrate = 0;
127
+ public receivingAudioBitrate = 0;
128
+ public receivingVideoBitrate = 0;
143
129
 
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
- ) {
152
- super();
153
- this.setMaxListeners(Infinity);
154
- }
130
+ public numberOfInboundRtpStreams = 0;
131
+ public numberOfInbundTracks = 0;
132
+ public numberOfOutboundRtpStreams = 0;
133
+ public numberOfOutboundTracks = 0;
134
+ public numberOfDataChannels = 0;
155
135
 
156
- public getSfu<T extends Record<string, unknown> = Record<string, unknown>>(): ObservedSfu<T> | undefined {
157
- const sfu = this.call.observer.observedSfus.get(this.sfuId ?? '');
136
+ public deltaNumberOfIssues = 0;
158
137
 
159
- if (!sfu) return;
160
-
161
- return sfu as ObservedSfu<T>;
162
- }
138
+ public totalScoreSum = 0;
139
+ public numberOfScoreMeasurements = 0;
140
+ // public totalNumberOfIssues = 0;
163
141
 
164
- public get joined() {
165
- return this._joined;
166
- }
142
+ public readonly report: ClientReport;
167
143
 
168
- public set joined(value: number | undefined) {
169
- this._joined = value;
170
-
171
- if (this._joined) {
172
- this.emit('joined');
173
- }
174
- }
144
+ public readonly mediaDevices: MetaData.MediaDeviceInfo[] = [];
145
+ public issues: ClientIssue[] = [];
175
146
 
176
- public get left() {
177
- return this._left;
178
- }
147
+ private _injections: Pick<ClientSample, 'clientEvents' | 'clientIssues' | 'extensionStats' | 'attachments' | 'clientMetaItems'> = {};
179
148
 
180
- public set left(value: number | undefined) {
181
- this._left = value;
182
-
183
- if (this._left) {
184
- this.emit('left');
185
- }
186
- }
149
+ private closeTimer?: ReturnType<typeof setTimeout>;
187
150
 
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
- }
151
+ public constructor(settings: ObservedClientSettings<AppData>, public readonly call: ObservedCall) {
152
+ super();
153
+ this.setMaxListeners(Infinity);
197
154
 
198
- public set coordinates(value: { latitude: number; longitude: number } | undefined) {
199
- this._model.coordinates = value;
200
- }
155
+ this.clientId = settings.clientId;
156
+ this.appData = settings.appData ?? {} as AppData;
157
+
158
+ this.detectors = new Detectors();
159
+ this.settings = {
160
+ closeClientIfIdleForMs: settings.closeClientIfIdleForMs,
161
+ };
201
162
 
202
- public get clientId(): string {
203
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
- return this._model.clientId!;
163
+ this.report = {
164
+ callId: this.call.callId,
165
+ clientId: this.clientId,
166
+ rttDistribution: {
167
+ gtOrEq300ms: 0,
168
+ lt50ms: 0,
169
+ lt150ms: 0,
170
+ lt300ms: 0,
171
+ count: 0,
172
+ sum: 0,
173
+ },
174
+ scoreDistribution: {
175
+ '0': 0,
176
+ '1': 0,
177
+ '2': 0,
178
+ '3': 0,
179
+ '4': 0,
180
+ '5': 0,
181
+ count: 0,
182
+ sum: 0,
183
+ },
184
+ issues: {},
185
+ totalAudioBytesReceived: 0,
186
+ totalVideoBytesReceived: 0,
187
+ totalAudioBytesSent: 0,
188
+ totalVideoBytesSent: 0,
189
+ totalDataChannelBytesReceived: 0,
190
+ totalDataChannelBytesSent: 0,
191
+ totalDataChannelMessagesReceived: 0,
192
+ totalDataChannelMessagesSent: 0,
193
+ totalInboundRtpBytesReceived: 0,
194
+ totalInboundRtpPacketsLost: 0,
195
+ totalInboundRtpPacketsReceived: 0,
196
+ totalOutboundRtpBytesSent: 0,
197
+ totalOutboundRtpPacketsSent: 0,
198
+ totalNumberOfIssues: 0,
199
+ };
205
200
  }
206
-
207
- public get roomId() {
208
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
209
- return this.call.roomId!;
201
+
202
+ public get numberOfPeerConnections() {
203
+ return this.observedPeerConnections.size;
210
204
  }
211
205
 
212
- public get serviceId() {
213
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
214
- return this.call.serviceId!;
206
+ public get score() {
207
+ return this.calculatedScore.value;
215
208
  }
216
209
 
217
- public get callId() {
218
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219
- return this.call.callId!;
220
- }
210
+ public close() {
211
+ if (this.closed) return;
212
+ this.closed = true;
221
213
 
222
- public get mediaUnitId() {
223
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
224
- return this._model.mediaUnitId!;
225
- }
214
+ this._injections.clientEvents?.forEach((clientEvent) => this._processClientEvent(clientEvent));
215
+ this._injections.clientIssues?.forEach((clientIssue) => this.addIssue(clientIssue));
216
+ this._injections.extensionStats?.forEach((extensionStat) => this.addExtensionStats(extensionStat));
217
+ this._injections.clientMetaItems?.forEach((clientMetaItem) => this.addMetadata(clientMetaItem));
226
218
 
227
- public get reports() {
228
- return this.call.reports;
229
- }
219
+ Array.from(this.observedPeerConnections.values()).forEach((peerConnection) => peerConnection.close());
220
+ if (!this.leftAt) {
221
+ this.leftAt = this.lastSampleTimestamp;
230
222
 
231
- public get userId() {
232
- return this._model.userId;
233
- }
223
+ if (this.leftAt) {
224
+ this.emit('left');
225
+ }
226
+ }
227
+ this.closedAt = Date.now();
234
228
 
235
- public set userId(userId: string | undefined) {
236
- this._model.userId = userId;
229
+ this.emit('close');
237
230
  }
238
231
 
239
- public get timeZoneOffsetInHours() {
240
- return this._timeZoneOffsetInHours;
241
- }
232
+ public accept(sample: ClientSample): void {
233
+ if (this.closed) throw new Error(`Client ${this.clientId} is closed`);
242
234
 
243
- public get marker() {
244
- return this._model.marker;
245
- }
235
+ if (this.closeTimer) {
236
+ clearTimeout(this.closeTimer);
237
+ this.closeTimer = undefined;
238
+ }
246
239
 
247
- public get browser() {
248
- return this._model.browser;
249
- }
240
+ const now = Date.now();
241
+ const elapsedInMs = now - this.updated;
242
+ const elapsedInSeconds = elapsedInMs / 1000;
243
+ let sumOfRtts = 0;
244
+ let numberOfRttMeasurements = 0;
245
+
246
+ ++this.acceptedSamples;
247
+
248
+ this.availableIncomingBitrate = 0;
249
+ this.availableOutgoingBitrate = 0;
250
+ this.deltaDataChannelBytesReceived = 0;
251
+ this.deltaDataChannelBytesSent = 0;
252
+ this.deltaDataChannelMessagesReceived = 0;
253
+ this.deltaDataChannelMessagesSent = 0;
254
+ this.deltaInboundPacketsLost = 0;
255
+ this.deltaInboundPacketsReceived = 0;
256
+ this.deltaOutboundPacketsSent = 0;
257
+ this.deltaReceivedAudioBytes = 0;
258
+ this.deltaReceivedVideoBytes = 0;
259
+ this.deltaSentAudioBytes = 0;
260
+ this.deltaSentVideoBytes = 0;
261
+ this.deltaTransportReceivedBytes = 0;
262
+ this.deltaTransportSentBytes = 0;
263
+ this.deltaRttLt50Measurements = 0;
264
+ this.deltaRttLt150Measurements = 0;
265
+ this.deltaRttLt300Measurements = 0;
266
+ this.deltaRttGtOrEq300Measurements = 0;
267
+ this.deltaNumberOfIssues = 0;
268
+
269
+ this.numberOfDataChannels = 0;
270
+ this.numberOfInboundRtpStreams = 0;
271
+ this.numberOfInbundTracks = 0;
272
+ this.numberOfOutboundRtpStreams = 0;
273
+ this.numberOfOutboundTracks = 0;
274
+ this.usingTURN = false;
275
+ this.usingTCP = false;
276
+ this.currentMinRttInMs = undefined;
277
+ this.currentMaxRttInMs = undefined;
250
278
 
251
- public get engine() {
252
- return this._model.engine;
253
- }
279
+ this._mergeInjections(sample);
254
280
 
255
- public get operationSystem() {
256
- return this._model.operationSystem;
257
- }
281
+ const clientEventsPostBuffer: ClientEvent[] = [];
258
282
 
259
- public get platform() {
260
- return this._model.platform;
261
- }
283
+ for (const clientEvent of sample.clientEvents ?? []) {
284
+ this._processClientEvent(clientEvent, clientEventsPostBuffer);
262
285
 
263
- public get acceptedSamples() {
264
- return this._acceptedSamples;
265
- }
286
+ this.call.observer.emit('client-event', this, clientEvent);
287
+ }
266
288
 
267
- public get closed() {
268
- return this._closed;
269
- }
289
+ for (const metaData of sample.clientMetaItems ?? []) {
290
+ this.addMetadata(metaData);
291
+ }
270
292
 
271
- public get peerConnections(): ReadonlyMap<string, ObservedPeerConnection> {
272
- return this._peerConnections;
273
- }
274
-
275
- public close() {
276
- if (this._closed) return;
277
- this._closed = true;
293
+ for (const issue of sample.clientIssues ?? []) {
294
+ this.addIssue(issue);
278
295
 
279
- Array.from(this._peerConnections.values()).forEach((peerConnection) => peerConnection.close());
296
+ ++this.deltaNumberOfIssues;
297
+ }
280
298
 
281
- this.emit('close');
282
- }
299
+ for (const extensionStat of sample.extensionStats ?? []) {
300
+ this.addExtensionStats(extensionStat);
301
+ }
283
302
 
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
+ for (const pcSample of sample.peerConnections ?? []) {
304
+ const observedPeerConnection = this._updatePeerConnection(pcSample);
303
305
 
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
- }
306
+ if (!observedPeerConnection) continue;
318
307
 
319
- public addIssue(issue: ClientIssue) {
320
- try {
308
+ this.deltaDataChannelBytesReceived += observedPeerConnection.deltaDataChannelBytesReceived;
309
+ this.deltaDataChannelBytesSent += observedPeerConnection.deltaDataChannelBytesSent;
310
+ this.deltaDataChannelMessagesReceived += observedPeerConnection.deltaDataChannelMessagesReceived;
311
+ this.deltaDataChannelMessagesSent += observedPeerConnection.deltaDataChannelMessagesSent;
312
+ this.deltaInboundPacketsLost += observedPeerConnection.deltaInboundPacketsLost;
313
+ this.deltaInboundPacketsReceived += observedPeerConnection.deltaInboundPacketsReceived;
314
+ this.deltaOutboundPacketsSent += observedPeerConnection.deltaOutboundPacketsSent;
315
+ this.deltaReceivedAudioBytes += observedPeerConnection.deltaReceivedAudioBytes;
316
+ this.deltaReceivedVideoBytes += observedPeerConnection.deltaReceivedVideoBytes;
317
+ this.deltaSentAudioBytes += observedPeerConnection.deltaSentAudioBytes;
318
+ this.deltaSentVideoBytes += observedPeerConnection.deltaSentVideoBytes;
319
+ this.deltaTransportReceivedBytes += observedPeerConnection.deltaTransportReceivedBytes;
320
+ this.deltaTransportSentBytes += observedPeerConnection.deltaTransportSentBytes;
321
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
- }
322
+ this.availableIncomingBitrate += observedPeerConnection.availableIncomingBitrate;
323
+ this.availableOutgoingBitrate += observedPeerConnection.availableOutgoingBitrate;
341
324
 
342
- this._addAndEmitIssue(issue);
343
- }
325
+ this.numberOfDataChannels += observedPeerConnection.observedDataChannels.size;
326
+ this.numberOfInbundTracks += observedPeerConnection.observedInboundTracks.size;
327
+ this.numberOfOutboundRtpStreams += observedPeerConnection.observedOutboundRtps.size;
328
+ this.numberOfOutboundTracks += observedPeerConnection.observedOutboundTracks.size;
329
+ this.numberOfInboundRtpStreams += observedPeerConnection.observedInboundRtps.size;
344
330
 
345
- 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();
331
+ if (observedPeerConnection.usingTURN) {
332
+ this.usingTURN = true;
333
+ }
334
+ if (observedPeerConnection.usingTCP) {
335
+ this.usingTCP = true;
336
+ }
351
337
 
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
- }
338
+ if (observedPeerConnection.currentRttInMs) {
339
+ if (this.currentMinRttInMs === undefined || observedPeerConnection.currentRttInMs < this.currentMinRttInMs) {
340
+ this.currentMinRttInMs = observedPeerConnection.currentRttInMs;
341
+ }
342
+ if (this.currentMaxRttInMs === undefined || observedPeerConnection.currentRttInMs > this.currentMaxRttInMs) {
343
+ this.currentMaxRttInMs = observedPeerConnection.currentRttInMs;
344
+ }
345
+ if (observedPeerConnection.currentRttInMs < 50) {
346
+ this.deltaRttLt50Measurements += 1;
347
+ this.report.rttDistribution.lt50ms += 1;
348
+ } else if (observedPeerConnection.currentRttInMs < 150) {
349
+ this.deltaRttLt150Measurements += 1;
350
+ this.report.rttDistribution.lt150ms += 1;
351
+ } else if (observedPeerConnection.currentRttInMs < 300) {
352
+ this.deltaRttLt300Measurements += 1;
353
+ this.report.rttDistribution.lt300ms += 1;
354
+ } else if (300 <= observedPeerConnection.currentRttInMs) {
355
+ this.deltaRttGtOrEq300Measurements += 1;
356
+ this.report.rttDistribution.gtOrEq300ms += 1;
357
+ }
358
+ this.report.rttDistribution.count += 1;
359
+ this.report.rttDistribution.sum += observedPeerConnection.currentRttInMs;
361
360
 
362
- if (this._model.userId) {
363
- if (sample.userId && sample.userId !== 'NULL' && sample.userId !== this._model.userId) {
364
- this._model.userId = sample.userId;
361
+ sumOfRtts += observedPeerConnection.currentRttInMs;
362
+ ++numberOfRttMeasurements;
365
363
  }
366
- } else if (sample.userId && sample.userId !== 'NULL') {
367
- this._model.userId = sample.userId;
368
- }
369
-
370
- if (this._model.marker !== sample.marker) {
371
- this._model.marker = sample.marker;
372
- this._peerConnections.forEach((peerConnection) => (peerConnection.marker = sample.marker));
373
364
  }
374
365
 
375
- if (this._timeZoneOffsetInHours !== sample.timeZoneOffsetInHours) {
376
- this._timeZoneOffsetInHours = sample.timeZoneOffsetInHours;
366
+ for (const clientEvent of clientEventsPostBuffer) {
367
+ this._processClientEvent(clientEvent);
377
368
  }
378
369
 
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
- }
370
+ // emit new attachments?
371
+ this.attachments = sample.attachments;
403
372
 
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
- }
373
+ this.report.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
374
+ this.report.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
375
+ this.report.totalDataChannelMessagesReceived += this.deltaDataChannelMessagesReceived;
376
+ this.report.totalDataChannelMessagesSent += this.deltaDataChannelMessagesSent;
377
+ this.report.totalInboundRtpPacketsLost += this.deltaInboundPacketsLost;
378
+ this.report.totalInboundRtpPacketsReceived += this.deltaInboundPacketsReceived;
379
+ this.report.totalOutboundRtpPacketsSent += this.deltaOutboundPacketsSent;
380
+ this.report.totalAudioBytesReceived += this.deltaReceivedAudioBytes;
381
+ this.report.totalVideoBytesReceived += this.deltaReceivedVideoBytes;
382
+ this.report.totalAudioBytesSent += this.deltaSentAudioBytes;
383
+ this.report.totalVideoBytesSent += this.deltaSentVideoBytes;
384
+ this.report.totalInboundRtpBytesReceived += this.deltaTransportReceivedBytes;
385
+ this.report.totalOutboundRtpBytesSent += this.deltaTransportSentBytes;
386
+ this.report.totalNumberOfIssues += this.deltaNumberOfIssues;
452
387
 
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
- }
388
+ this.receivingAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (elapsedInSeconds);
389
+ this.receivingVideoBitrate = (this.deltaReceivedVideoBytes * 8) / (elapsedInSeconds);
390
+ this.sendingAudioBitrate = (this.deltaSentAudioBytes * 8) / (elapsedInSeconds);
391
+ this.sendingVideoBitrate = (this.deltaSentVideoBytes * 8) / (elapsedInSeconds);
392
+ this.currentAvgRttInMs = 0 < numberOfRttMeasurements ? sumOfRtts / numberOfRttMeasurements : undefined;
476
393
 
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
- }
394
+ this.calculatedScore.value = sample.score;
496
395
 
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
- }
396
+ if (sample.score !== undefined) {
397
+ const bucket = Math.floor(sample.score).toString() as '0' | '1' | '2' | '3' | '4' | '5';
515
398
 
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
- });
399
+ this.report.scoreDistribution[bucket] = (this.report.scoreDistribution[bucket] ?? 0) + 1;
400
+ this.report.scoreDistribution.count += 1;
401
+ this.report.scoreDistribution.sum += sample.score;
528
402
  }
403
+
404
+ this.detectors.update();
529
405
 
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;
406
+ this.lastSampleTimestamp = sample.timestamp;
407
+ // emit update
408
+ this.emit('update',
409
+ sample,
410
+ now - this.updated,
411
+ );
412
+ this.updated = now;
536
413
 
537
- // if it is joined before and it is joined again
538
- if (lastJoined && this.joined && lastJoined !== this.joined) {
539
- this.emit('rejoined', { lastJoined });
414
+ // if result changed after update
415
+ if (this.calculatedScore.value) {
416
+ this.totalScoreSum += this.calculatedScore.value;
417
+ ++this.numberOfScoreMeasurements;
418
+ }
419
+
420
+ if (this.settings.closeClientIfIdleForMs !== undefined) {
421
+ this.closeTimer = setTimeout(() => {
422
+ this.close();
423
+ }, this.settings.closeClientIfIdleForMs);
424
+ }
425
+ }
426
+
427
+ private _processClientEvent(event: ClientEvent, postBuffer?: ClientEvent[]) {
428
+ // eslint-disable-next-line no-console
429
+ // console.warn('ClientEvent', event);
430
+ switch (event.type) {
431
+ case ClientEventTypes.CLIENT_JOINED: {
432
+ if (event.timestamp) {
433
+ if (!this.joinedAt) {
434
+ this.joinedAt = event.timestamp;
435
+ this.emit('joined');
436
+ } else if (this.joinedAt < event.timestamp) {
437
+ this.emit('rejoined', event.timestamp);
438
+ } else {
439
+ this.joinedAt = event.timestamp;
440
+ logger.warn(`Client ${this.clientId} joinedAt timestamp was updated to ${event.timestamp}. the joined event will not be emitted.`);
540
441
  }
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
442
  }
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;
443
+ logger.debug('Client %s joined at %o', this.clientId, event);
444
+ break;
445
+ }
446
+ case ClientEventTypes.CLIENT_LEFT: {
447
+ if (event.timestamp) {
448
+ if (!this.leftAt) {
449
+ this.leftAt = event.timestamp;
450
+ this.emit('left');
451
+ } else {
452
+ logger.warn(`Client ${this.clientId} leftAt timestamp was already set`);
453
+ }
454
+
558
455
  }
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;
456
+ logger.debug('Client %s left at %o', this.clientId, event);
457
+ break;
458
+ }
459
+ case ClientEventTypes.PEER_CONNECTION_OPENED: {
460
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
461
+
462
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string') {
463
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
464
+
465
+ if (observedPeerConnection) {
466
+ observedPeerConnection.openedAt = event.timestamp;
467
+ } else if (postBuffer) {
468
+ postBuffer.push(event);
469
+ } else {
470
+ logger.warn('Received PEER_CONNECTION_OPENED event without a corresponding observedPeerConnection: %o', event);
471
+ }
472
+
566
473
  }
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;
474
+ break;
475
+ }
476
+ case ClientEventTypes.PEER_CONNECTION_CLOSED: {
477
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
478
+
479
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string') {
480
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
481
+
482
+ if (observedPeerConnection) {
483
+ observedPeerConnection.closedAt = event.timestamp;
484
+ } else if (postBuffer) {
485
+ postBuffer.push(event);
486
+ } else {
487
+ logger.warn('Received PEER_CONNECTION_CLOSED event without a corresponding observedPeerConnection: %o', event);
488
+ }
575
489
  }
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;
490
+ break;
491
+ }
492
+ case ClientEventTypes.MEDIA_TRACK_ADDED: {
493
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
494
+
495
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') {
496
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
497
+ const observedTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId) ?? observedPeerConnection?.observedOutboundTracks.get(payload.trackId);
498
+
499
+ if (observedTrack) {
500
+ observedTrack.addedAt = event.timestamp;
501
+ } else if (postBuffer) {
502
+ postBuffer.push(event);
503
+ } else {
504
+ logger.warn('Received MEDIA_TRACK_ADDED event without a corresponding observedPeerConnection or observedTrack: %o', event);
505
+ }
584
506
  }
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}`);
507
+ break;
508
+ }
509
+ case ClientEventTypes.MEDIA_TRACK_REMOVED: {
510
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
511
+
512
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') {
513
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
514
+ const observedTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId) ?? observedPeerConnection?.observedOutboundTracks.get(payload.trackId);
515
+
516
+ if (observedTrack) {
517
+ observedTrack.removedAt = event.timestamp;
518
+ } else if (postBuffer) {
519
+ postBuffer.push(event);
520
+ } else {
521
+ logger.warn('Received MEDIA_TRACK_REMOVED event without a corresponding observedPeerConnection or observedTrack: %o', event);
601
522
  }
602
- break;
603
523
  }
524
+ break;
604
525
  }
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);
526
+ case ClientEventTypes.DATA_CHANNEL_OPEN: {
527
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
528
+
529
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.dataChannelId && typeof payload.dataChannelId === 'string') {
530
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
531
+ const observedDataChannel = observedPeerConnection?.observedDataChannels.get(payload.dataChannelId);
532
+
533
+ if (observedDataChannel) {
534
+ observedDataChannel.addedAt = event.timestamp;
535
+ } else if (postBuffer) {
536
+ postBuffer.push(event);
537
+ } else {
538
+ logger.warn('Received DATA_CHANNEL_OPENED event without a corresponding observedPeerConnection or observedDataChannel: %o', event);
539
+ }
540
+ }
541
+ break;
675
542
  }
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);
543
+ case ClientEventTypes.DATA_CHANNEL_CLOSED: {
544
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
545
+
546
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.dataChannelId && typeof payload.dataChannelId === 'string') {
547
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
548
+ const observedDataChannel = observedPeerConnection?.observedDataChannels.get(payload.dataChannelId);
549
+
550
+ if (observedDataChannel) {
551
+ observedDataChannel.removedAt = event.timestamp;
552
+ } else if (postBuffer) {
553
+ postBuffer.push(event);
554
+ } else {
555
+ logger.warn('Received DATA_CHANNEL_CLOSE event without a corresponding observedPeerConnection or observedDataChannel: %o', event);
556
+ }
557
+ }
558
+ break;
706
559
  }
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);
560
+ case ClientEventTypes.MEDIA_TRACK_MUTED: {
561
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
562
+
563
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') {
564
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
565
+ const observedInboundTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId);
566
+ const observedOutboundTrack = observedPeerConnection?.observedOutboundTracks.get(payload.trackId);
567
+
568
+ if (observedPeerConnection) {
569
+ if (observedInboundTrack) {
570
+ observedInboundTrack.muted = true;
571
+ observedPeerConnection?.emit('muted-inbound-track', observedInboundTrack);
572
+ } else if (observedOutboundTrack) {
573
+ observedOutboundTrack.muted = true;
574
+ observedPeerConnection?.emit('muted-outbound-track', observedOutboundTrack);
575
+ }
576
+ } else if (postBuffer) {
577
+ postBuffer.push(event);
578
+ } else {
579
+ logger.warn('Received MEDIA_TRACK_MUTED event without a corresponding observedPeerConnection or observedTrack: %o', event);
580
+ }
581
+ }
726
582
 
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}`);
583
+ break;
731
584
  }
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)}`);
738
-
739
- continue;
585
+ case ClientEventTypes.MEDIA_TRACK_UNMUTED: {
586
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
587
+
588
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') {
589
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
590
+ const observedInboundTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId);
591
+ const observedOutboundTrack = observedPeerConnection?.observedOutboundTracks.get(payload.trackId);
592
+
593
+ if (observedPeerConnection) {
594
+ if (observedInboundTrack) {
595
+ observedInboundTrack.muted = false;
596
+ observedPeerConnection?.emit('unmuted-inbound-track', observedInboundTrack);
597
+ } else if (observedOutboundTrack) {
598
+ observedOutboundTrack.muted = false;
599
+ observedPeerConnection?.emit('unmuted-outbound-track', observedOutboundTrack);
600
+ }
601
+ } else if (postBuffer) {
602
+ postBuffer.push(event);
603
+ } else {
604
+ logger.warn('Received MEDIA_TRACK_UNMUTED event without a corresponding observedPeerConnection or observedTrack: %o', event);
605
+ }
606
+ }
607
+ break;
740
608
  }
609
+ case ClientEventTypes.ICE_CONNECTION_STATE_CHANGED: {
610
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
741
611
 
742
- try {
743
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
612
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.iceConnectionState && typeof payload.iceConnectionState === 'string') {
613
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
744
614
 
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 ?? '');
757
-
758
- if (remoteOutboundTrack) {
759
- inboundAudioTrack.remoteOutboundTrack = remoteOutboundTrack;
760
-
761
- inboundAudioTrack.once('close', () => {
762
- remoteOutboundTrack?.remoteInboundTracks.delete(inboundAudioTrack.trackId ?? '');
615
+ if (observedPeerConnection) {
616
+ observedPeerConnection.iceConnectionState = payload.iceConnectionState as 'new' | 'checking' | 'connected' | 'completed' | 'failed' | 'disconnected' | 'closed';
617
+ observedPeerConnection.emit('iceconnectionstatechange', {
618
+ state: payload.iceConnectionState,
763
619
  });
764
- remoteOutboundTrack.remoteInboundTracks.set(inboundAudioTrack.trackId ?? '', inboundAudioTrack);
620
+ } else if (postBuffer) {
621
+ postBuffer.push(event);
622
+ } else {
623
+ logger.warn('Received ICE_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o', event);
765
624
  }
766
625
  }
767
- } catch (err) {
768
- logger.error(`Error creating inbound audio track: ${(err as Error)?.message}`);
626
+ break;
769
627
  }
770
- }
628
+ case ClientEventTypes.ICE_GATHERING_STATE_CHANGED: {
629
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
771
630
 
772
- for (const track of sample.inboundVideoTracks ?? []) {
773
- if (!track.peerConnectionId || !track.trackId) {
774
- logger.warn(`InboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
631
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.iceGatheringState && typeof payload.iceGatheringState === 'string') {
632
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
775
633
 
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 ?? '');
634
+ if (observedPeerConnection) {
635
+ observedPeerConnection.iceGatheringState = payload.iceGatheringState as 'new' | 'gathering' | 'complete';
636
+ observedPeerConnection.emit('icegatheringstatechange', {
637
+ state: payload.iceGatheringState,
806
638
  });
807
- remoteOutboundTrack.remoteInboundTracks.set(inboundVideoTrack.trackId ?? '', inboundVideoTrack);
639
+ } else if (postBuffer) {
640
+ postBuffer.push(event);
641
+ } else {
642
+ logger.warn('Received ICE_GATHERING_STATE_CHANGED event without a corresponding observedPeerConnection: %o', event);
808
643
  }
809
644
  }
810
- } catch (err) {
811
- logger.error(`Error creating inbound video track: ${(err as Error)?.message}`);
645
+ break;
812
646
  }
813
- }
647
+ case ClientEventTypes.PEER_CONNECTION_STATE_CHANGED: {
648
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
814
649
 
815
- for (const track of sample.outboundAudioTracks ?? []) {
816
- if (!track.peerConnectionId || !track.trackId) {
817
- logger.warn(`OutboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
650
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.peerConnectionState && typeof payload.peerConnectionState === 'string') {
651
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
818
652
 
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);
653
+ if (observedPeerConnection) {
654
+ observedPeerConnection.connectionState = payload.peerConnectionState as 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';
655
+ observedPeerConnection.emit('connectionstatechange', {
656
+ state: payload.peerConnectionState,
657
+ });
658
+ } else if (postBuffer) {
659
+ postBuffer.push(event);
660
+ } else {
661
+ logger.warn('Received PEER_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o', event);
662
+ }
836
663
  }
837
-
838
- } catch (err) {
839
- logger.error(`Error creating outbound audio track: ${(err as Error)?.message}`);
664
+ break;
840
665
  }
841
-
842
666
  }
667
+
668
+ this.emit('clientEvent', event);
669
+ }
843
670
 
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);
671
+ public injectMetaData(metaData: ClientMetaData) {
672
+ if (this.closed) return;
673
+
674
+ if (!this._injections.clientMetaItems) this._injections.clientMetaItems = [];
853
675
 
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);
676
+ this._injections.clientMetaItems.push(metaData);
677
+ }
862
678
 
863
- if (outboundVideoTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundVideoTrack.has(outboundVideoTrack.sfuStreamId)) {
864
- this.call.sfuStreamIdToOutboundVideoTrack.set(outboundVideoTrack.sfuStreamId, outboundVideoTrack);
865
- }
679
+ public injectEvent(event: ClientEvent) {
680
+ if (this.closed) return;
866
681
 
867
- } catch (err) {
868
- logger.error(`Error creating outbound video track: ${(err as Error)?.message}`);
869
- }
870
- }
682
+ if (!this._injections.clientEvents) this._injections.clientEvents = [];
871
683
 
872
- for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
873
- if (!iceLocalCandidate.peerConnectionId) {
874
- logger.warn(`Local ice candidate without peerConnectionId: ${JSON.stringify(iceLocalCandidate)}`);
875
- continue;
876
- }
877
- const peerConnection = this._peerConnections.get(iceLocalCandidate.peerConnectionId);
684
+ this._injections.clientEvents.push(event);
685
+ }
878
686
 
879
- if (!peerConnection) {
880
- logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
881
- continue;
882
- }
687
+ public injectIssue(issue: ClientIssue) {
688
+ if (this.closed) return;
883
689
 
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;
891
- }
892
- const peerConnection = this._peerConnections.get(iceRemoteCandidate.peerConnectionId);
690
+ if (!this._injections.clientIssues) this._injections.clientIssues = [];
893
691
 
894
- if (!peerConnection) {
895
- logger.debug(`Peer connection ${iceRemoteCandidate.peerConnectionId} not found for ice remote candidate ${iceRemoteCandidate.id}`);
896
- continue;
897
- }
692
+ this._injections.clientIssues.push(issue);
693
+ }
898
694
 
899
- peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate, sample.timestamp);
900
- }
695
+ public injectExtensionStat(stat: ExtensionStat) {
696
+ if (this.closed) return;
901
697
 
902
- for (const candidatePair of sample.iceCandidatePairs ?? []) {
903
- const peerConnection = this._peerConnections.get(candidatePair.peerConnectionId);
698
+ if (!this._injections.extensionStats) this._injections.extensionStats = [];
904
699
 
905
- if (!peerConnection) {
906
- logger.debug(`Peer connection ${candidatePair.peerConnectionId} not found for ice candidate pair ${candidatePair.localCandidateId}, ${candidatePair.remoteCandidateId}`);
700
+ this._injections.extensionStats.push(stat);
701
+ }
907
702
 
908
- continue;
909
- }
703
+ public injectAttachment(key: string, value: unknown) {
704
+ if (this.closed) return;
910
705
 
911
- peerConnection.ICE.update(candidatePair, sample.timestamp);
912
- }
706
+ if (!this._injections.attachments) this._injections.attachments = {};
913
707
 
914
- for (const dataChannel of sample.dataChannels ?? []) {
915
- if (!dataChannel.peerConnectionId || !dataChannel.dataChannelIdentifier) {
916
- logger.warn(`DataChannel without peerConnectionId or dataChannelIdentifier: ${JSON.stringify(dataChannel)}`);
708
+ this._injections.attachments[key] = value;
709
+
710
+ }
917
711
 
918
- continue;
712
+ public addMetadata(metadata: ClientMetaData) {
713
+ if (this.closed) return;
714
+
715
+ switch (metadata.type) {
716
+ case ClientMetaTypes.BROWSER: {
717
+ this.browser = parseJsonAs(metadata.payload);
718
+ break;
919
719
  }
920
-
921
- try {
922
- const peerConnection = this._peerConnections.get(dataChannel.peerConnectionId) ?? this._createPeerConnection(dataChannel.peerConnectionId);
923
-
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}`);
720
+ case ClientMetaTypes.ENGINE: {
721
+ this.engine = parseJsonAs(metadata.payload);
722
+ break;
931
723
  }
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
-
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;
724
+ case ClientMetaTypes.PLATFORM: {
725
+ this.platform = parseJsonAs(metadata.payload);
726
+ break;
947
727
  }
948
-
949
- this.ωpendingPeerConnectionTimestamps = newPendingTimestamps;
950
- }
951
-
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;
728
+ case ClientMetaTypes.OPERATION_SYSTEM: {
729
+ this.operationSystem = parseJsonAs(metadata.payload);
730
+ break;
970
731
  }
971
-
972
- this.ωpendingMediaTrackTimestamps = newPendingTimestamps;
973
732
  }
974
733
 
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();
734
+ this.call.observer.emit('client-metadata', this, metadata);
735
+ }
986
736
 
987
- continue;
988
- }
737
+ public addIssue(issue: ClientIssue) {
738
+ if (this.closed) return;
989
739
 
990
- track.visited = false;
991
- }
992
- for (const track of peerConnection.inboundVideoTracks.values()) {
993
- if (!track.visited) {
994
- track.close();
740
+ this.report.issues[issue.type] = (this.report.issues[issue.type] ?? 0) + 1;
995
741
 
996
- continue;
997
- }
742
+ this.emit('issue', issue);
743
+ this.call.observer.emit('client-issue', this, issue);
744
+ }
998
745
 
999
- track.visited = false;
1000
- }
1001
- for (const track of peerConnection.outboundAudioTracks.values()) {
1002
- if (!track.visited) {
1003
- track.close();
746
+ public addExtensionStats(stats: ExtensionStat) {
747
+ this.call.observer.emit('client-extension-stats', this, stats);
748
+ this.emit('extensionStats', stats);
749
+ }
1004
750
 
1005
- continue;
1006
- }
751
+ private _updatePeerConnection(sample: PeerConnectionSample): ObservedPeerConnection | undefined {
752
+ let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId);
1007
753
 
1008
- track.visited = false;
754
+ if (!observedPeerConnection) {
755
+ if (!sample.peerConnectionId) {
756
+ return (logger.warn(
757
+ `ObservedClient received an invalid PeerConnectionSample (missing peerConnectionId field). ClientId: ${this.clientId}, CallId: ${this.call.callId}`,
758
+ sample
759
+ ), void 0);
1009
760
  }
1010
- for (const dataChannel of peerConnection.dataChannels.values()) {
1011
- if (!dataChannel.visited) {
1012
- dataChannel.close();
1013
761
 
1014
- continue;
1015
- }
762
+ const newObservedPeerConnection = new ObservedPeerConnection(sample.peerConnectionId, this);
763
+ const onInboundTrackRemoved = (track: ObservedInboundTrack) => {
764
+ this.emit('trackreport', {
765
+ direction: 'inbound',
766
+ ...track.report,
767
+ });
768
+ };
769
+ const onOutboundTrackRemoved = (track: ObservedOutboundTrack) => {
770
+ this.emit('trackreport', {
771
+ direction: 'outbound',
772
+ ...track.report,
773
+ });
774
+ };
1016
775
 
1017
- dataChannel.visited = false;
1018
- }
776
+ newObservedPeerConnection.once('close', () => {
777
+ newObservedPeerConnection?.off('removed-inbound-track', onInboundTrackRemoved);
778
+ newObservedPeerConnection?.off('removed-outbound-track', onOutboundTrackRemoved);
779
+ this.observedPeerConnections.delete(sample.peerConnectionId);
780
+ });
781
+ newObservedPeerConnection.on('removed-inbound-track', onInboundTrackRemoved);
782
+ newObservedPeerConnection.on('removed-outbound-track', onOutboundTrackRemoved);
783
+ this.observedPeerConnections.set(sample.peerConnectionId, newObservedPeerConnection);
784
+
785
+ this.emit('newpeerconnection', newObservedPeerConnection);
1019
786
 
1020
- peerConnection.visited = false;
787
+ observedPeerConnection = newObservedPeerConnection;
1021
788
  }
1022
789
 
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;
1039
-
1040
- this.outboundAudioBitrate = 0;
1041
- this.outboundVideoBitrate = 0;
1042
- this.inboundAudioBitrate = 0;
1043
- this.inboundVideoBitrate = 0;
1044
- let sumRttInMs = 0;
1045
- let anyPeerConnectionUsingTurn = false;
790
+ observedPeerConnection.accept(sample);
1046
791
 
1047
- let minPcScore: number | undefined;
1048
- let maxPcScore: number | undefined;
1049
- let numberOfScoredPeerConnections = 0;
792
+ return observedPeerConnection;
793
+ }
1050
794
 
1051
- for (const peerConnection of this._peerConnections.values()) {
1052
- if (peerConnection.closed) continue;
795
+ private _mergeInjections(sample: ClientSample): ClientSample {
796
+ if (this.closed) return sample;
1053
797
 
1054
- peerConnection.updateMetrics();
798
+ if (this._injections.clientEvents) {
799
+ if (!sample.clientEvents) sample.clientEvents = [];
800
+ sample.clientEvents.push(...this._injections.clientEvents);
1055
801
 
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;
1065
-
1066
- this.availableIncomingBitrate += peerConnection.availableIncomingBitrate ?? 0;
1067
- this.availableOutgoingBitrate += peerConnection.availableOutgoingBitrate ?? 0;
1068
-
1069
- sumRttInMs += peerConnection.avgRttInMs ?? 0;
1070
-
1071
- anyPeerConnectionUsingTurn ||= peerConnection.usingTURN;
802
+ this._injections.clientEvents = undefined;
803
+ }
1072
804
 
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;
805
+ if (this._injections.clientIssues) {
806
+ if (!sample.clientIssues) sample.clientIssues = [];
807
+ sample.clientIssues.push(...this._injections.clientIssues);
808
+
809
+ this._injections.clientIssues = undefined;
810
+ }
1076
811
 
1077
- ++numberOfScoredPeerConnections;
1078
- }
812
+ if (this._injections.extensionStats) {
813
+ if (!sample.extensionStats) sample.extensionStats = [];
814
+ sample.extensionStats.push(...this._injections.extensionStats);
815
+
816
+ this._injections.extensionStats = undefined;
1079
817
  }
1080
818
 
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,
1088
- };
1089
- this.emit('score', this.score);
819
+ if (this._injections.attachments) {
820
+ if (!sample.attachments) sample.attachments = {};
821
+ Object.assign(sample.attachments, this._injections.attachments);
822
+
823
+ this._injections.attachments = undefined;
824
+ }
1090
825
 
1091
- this.usingTURN = anyPeerConnectionUsingTurn === true;
826
+ if (this._injections.clientMetaItems) {
827
+ if (!sample.clientMetaItems) sample.clientMetaItems = [];
828
+ sample.clientMetaItems.push(...this._injections.clientMetaItems);
829
+
830
+ this._injections.clientMetaItems = undefined;
831
+ }
832
+
833
+ return sample;
834
+ }
835
+
836
+ // public resetSummaryMetrics() {
837
+ // this.totalDataChannelBytesReceived = 0;
838
+ // this.totalDataChannelBytesSent = 0;
839
+ // this.totalDataChannelMessagesReceived = 0;
840
+ // this.totalDataChannelMessagesSent = 0;
841
+ // this.totalInboundPacketsLost = 0;
842
+ // this.totalInboundPacketsReceived = 0;
843
+ // this.totalOutboundPacketsSent = 0;
844
+ // this.totalReceivedAudioBytes = 0;
845
+ // this.totalReceivedVideoBytes = 0;
846
+ // this.totalSentAudioBytes = 0;
847
+ // this.totalSentVideoBytes = 0;
848
+ // this.totalSentBytes = 0;
849
+ // this.totalReceivedBytes = 0;
1092
850
 
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;
851
+ // this.totalNumberOfIssues = 0;
1106
852
 
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
- }
1124
-
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);
1135
-
1136
- if (track) track.ωpendingIssuesForScores.push(issue);
1137
- } else {
1138
- peerConnection.ωpendingIssuesForScores.push(issue);
1139
- }
1140
- }
1141
- }
1142
-
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);
1151
-
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);
853
+ // this.totalScoreSum = 0;
854
+ // this.numberOfScoreMeasurements = 0;
855
+ // }
856
+
857
+ // public createSummary(): ObservedClientSummary {
858
+ // return {
859
+ // totalRttLt50Measurements: this.totalRttLt50Measurements,
860
+ // totalRttLt150Measurements: this.totalRttLt150Measurements,
861
+ // totalRttLt300Measurements: this.totalRttLt300Measurements,
862
+ // totalRttGtOrEq300Measurements: this.totalRttGtOrEq300Measurements,
863
+ // totalDataChannelBytesReceived: this.totalDataChannelBytesReceived,
864
+ // totalDataChannelBytesSent: this.totalDataChannelBytesSent,
865
+ // totalDataChannelMessagesReceived: this.totalDataChannelMessagesReceived,
866
+ // totalDataChannelMessagesSent: this.totalDataChannelMessagesSent,
867
+ // totalInboundPacketsLost: this.totalInboundPacketsLost,
868
+ // totalInboundPacketsReceived: this.totalInboundPacketsReceived,
869
+ // totalOutboundPacketsSent: this.totalOutboundPacketsSent,
870
+ // totalReceivedAudioBytes: this.totalReceivedAudioBytes,
871
+ // totalReceivedVideoBytes: this.totalReceivedVideoBytes,
872
+ // totalSentAudioBytes: this.totalSentAudioBytes,
873
+ // totalSentVideoBytes: this.totalSentVideoBytes,
874
+ // totalSentBytes: this.totalSentBytes,
875
+ // totalReceivedBytes: this.totalReceivedBytes,
876
+
877
+ // numberOfIssues: this.totalNumberOfIssues,
1168
878
 
1169
- return result;
1170
- }
1171
- }
879
+ // totalScoreSum: this.totalScoreSum,
880
+ // numberOfScoreMeasurements: this.numberOfScoreMeasurements,
881
+ // };
882
+ // }
883
+ }