@observertc/observer-js 0.42.8 → 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 -24
  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 +716 -1002
  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 -88
  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,1169 +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[] = [];
143
-
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
- }
125
+ public sendingAudioBitrate = 0;
126
+ public sendingVideoBitrate = 0;
127
+ public receivingAudioBitrate = 0;
128
+ public receivingVideoBitrate = 0;
155
129
 
156
- public getSfu<T extends Record<string, unknown> = Record<string, unknown>>(): ObservedSfu<T> | undefined {
157
- const sfu = this.call.observer.observedSfus.get(this.sfuId ?? '');
130
+ public numberOfInboundRtpStreams = 0;
131
+ public numberOfInbundTracks = 0;
132
+ public numberOfOutboundRtpStreams = 0;
133
+ public numberOfOutboundTracks = 0;
134
+ public numberOfDataChannels = 0;
158
135
 
159
- if (!sfu) return;
160
-
161
- return sfu as ObservedSfu<T>;
162
- }
136
+ public deltaNumberOfIssues = 0;
163
137
 
164
- public get joined() {
165
- return this._joined;
166
- }
138
+ public totalScoreSum = 0;
139
+ public numberOfScoreMeasurements = 0;
140
+ // public totalNumberOfIssues = 0;
167
141
 
168
- public set joined(value: number | undefined) {
169
- this._joined = value;
170
-
171
- if (this._joined) {
172
- this.emit('joined');
173
- }
174
- }
142
+ public readonly report: ClientReport;
175
143
 
176
- public get left() {
177
- return this._left;
178
- }
144
+ public readonly mediaDevices: MetaData.MediaDeviceInfo[] = [];
145
+ public issues: ClientIssue[] = [];
179
146
 
180
- public set left(value: number | undefined) {
181
- this._left = value;
182
-
183
- if (this._left) {
184
- this.emit('left');
185
- }
186
- }
147
+ private _injections: Pick<ClientSample, 'clientEvents' | 'clientIssues' | 'extensionStats' | 'attachments' | 'clientMetaItems'> = {};
187
148
 
188
- public get sfu(): ObservedSfu | undefined {
189
- if (!this.sfuId) return;
190
-
191
- return this.call.observer.observedSfus.get(this.sfuId);
192
- }
149
+ private closeTimer?: ReturnType<typeof setTimeout>;
193
150
 
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
- }
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;
427
387
 
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
- }
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 });
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;
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.`);
544
441
  }
545
- break;
546
442
  }
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;
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;
634
+ if (observedPeerConnection) {
635
+ observedPeerConnection.iceGatheringState = payload.iceGatheringState as 'new' | 'gathering' | 'complete';
636
+ observedPeerConnection.emit('icegatheringstatechange', {
637
+ state: payload.iceGatheringState,
638
+ });
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);
643
+ }
644
+ }
645
+ break;
783
646
  }
647
+ case ClientEventTypes.PEER_CONNECTION_STATE_CHANGED: {
648
+ const payload = parseJsonAs<Record<string, unknown>>(event.payload);
784
649
 
785
- try {
786
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
650
+ if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.peerConnectionState && typeof payload.peerConnectionState === 'string') {
651
+ const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId);
787
652
 
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 ?? '');
653
+ if (observedPeerConnection) {
654
+ observedPeerConnection.connectionState = payload.peerConnectionState as 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';
655
+ observedPeerConnection.emit('connectionstatechange', {
656
+ state: payload.peerConnectionState,
806
657
  });
807
- remoteOutboundTrack.remoteInboundTracks.set(inboundVideoTrack.trackId ?? '', inboundVideoTrack);
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);
808
662
  }
809
663
  }
810
- } catch (err) {
811
- logger.error(`Error creating inbound video track: ${(err as Error)?.message}`);
664
+ break;
812
665
  }
813
666
  }
667
+
668
+ this.emit('clientEvent', event);
669
+ }
814
670
 
815
- for (const track of sample.outboundAudioTracks ?? []) {
816
- if (!track.peerConnectionId || !track.trackId) {
817
- logger.warn(`OutboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
671
+ public injectMetaData(metaData: ClientMetaData) {
672
+ if (this.closed) return;
673
+
674
+ if (!this._injections.clientMetaItems) this._injections.clientMetaItems = [];
818
675
 
819
- continue;
820
- }
676
+ this._injections.clientMetaItems.push(metaData);
677
+ }
821
678
 
822
- try {
823
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
679
+ public injectEvent(event: ClientEvent) {
680
+ if (this.closed) return;
824
681
 
825
- if (!peerConnection) continue;
826
-
827
- const outboundAudioTrack = peerConnection.outboundAudioTracks.get(track.trackId) ?? peerConnection.createOutboundAudioTrack({
828
- trackId: track.trackId,
829
- sfuStreamId: track.sfuStreamId,
830
- });
682
+ if (!this._injections.clientEvents) this._injections.clientEvents = [];
831
683
 
832
- outboundAudioTrack.update(track, sample.timestamp);
684
+ this._injections.clientEvents.push(event);
685
+ }
833
686
 
834
- if (outboundAudioTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundAudioTrack.has(outboundAudioTrack.sfuStreamId)) {
835
- this.call.sfuStreamIdToOutboundAudioTrack.set(outboundAudioTrack.sfuStreamId, outboundAudioTrack);
836
- }
687
+ public injectIssue(issue: ClientIssue) {
688
+ if (this.closed) return;
837
689
 
838
- } catch (err) {
839
- logger.error(`Error creating outbound audio track: ${(err as Error)?.message}`);
840
- }
841
-
842
- }
690
+ if (!this._injections.clientIssues) this._injections.clientIssues = [];
843
691
 
844
- for (const track of sample.outboundVideoTracks ?? []) {
845
- if (!track.peerConnectionId || !track.trackId) {
846
- logger.warn(`OutboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
692
+ this._injections.clientIssues.push(issue);
693
+ }
847
694
 
848
- continue;
849
- }
695
+ public injectExtensionStat(stat: ExtensionStat) {
696
+ if (this.closed) return;
850
697
 
851
- try {
852
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
698
+ if (!this._injections.extensionStats) this._injections.extensionStats = [];
853
699
 
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);
700
+ this._injections.extensionStats.push(stat);
701
+ }
862
702
 
863
- if (outboundVideoTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundVideoTrack.has(outboundVideoTrack.sfuStreamId)) {
864
- this.call.sfuStreamIdToOutboundVideoTrack.set(outboundVideoTrack.sfuStreamId, outboundVideoTrack);
865
- }
703
+ public injectAttachment(key: string, value: unknown) {
704
+ if (this.closed) return;
866
705
 
867
- } catch (err) {
868
- logger.error(`Error creating outbound video track: ${(err as Error)?.message}`);
869
- }
870
- }
706
+ if (!this._injections.attachments) this._injections.attachments = {};
871
707
 
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);
708
+ this._injections.attachments[key] = value;
709
+
710
+ }
878
711
 
879
- if (!peerConnection) {
880
- logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
881
- 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;
882
719
  }
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;
720
+ case ClientMetaTypes.ENGINE: {
721
+ this.engine = parseJsonAs(metadata.payload);
722
+ break;
891
723
  }
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;
724
+ case ClientMetaTypes.PLATFORM: {
725
+ this.platform = parseJsonAs(metadata.payload);
726
+ break;
897
727
  }
898
-
899
- peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate, sample.timestamp);
900
- }
901
-
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;
728
+ case ClientMetaTypes.OPERATION_SYSTEM: {
729
+ this.operationSystem = parseJsonAs(metadata.payload);
730
+ break;
909
731
  }
910
-
911
- peerConnection.ICE.update(candidatePair, sample.timestamp);
912
732
  }
913
733
 
914
- for (const dataChannel of sample.dataChannels ?? []) {
915
- if (!dataChannel.peerConnectionId || !dataChannel.dataChannelIdentifier) {
916
- logger.warn(`DataChannel without peerConnectionId or dataChannelIdentifier: ${JSON.stringify(dataChannel)}`);
734
+ this.call.observer.emit('client-metadata', this, metadata);
735
+ }
917
736
 
918
- continue;
919
- }
737
+ public addIssue(issue: ClientIssue) {
738
+ if (this.closed) return;
920
739
 
921
- try {
922
- const peerConnection = this._peerConnections.get(dataChannel.peerConnectionId) ?? this._createPeerConnection(dataChannel.peerConnectionId);
740
+ this.report.issues[issue.type] = (this.report.issues[issue.type] ?? 0) + 1;
923
741
 
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}`);
931
- }
932
- }
742
+ this.emit('issue', issue);
743
+ this.call.observer.emit('client-issue', this, issue);
744
+ }
933
745
 
934
- // try to set the timestamps of the peer connections
935
- if (0 < this.ωpendingPeerConnectionTimestamps.length) {
936
- const newPendingTimestamps: PendingPeerConnectionTimestamp[] = [];
746
+ public addExtensionStats(stats: ExtensionStat) {
747
+ this.call.observer.emit('client-extension-stats', this, stats);
748
+ this.emit('extensionStats', stats);
749
+ }
937
750
 
938
- for (const pendingTimestamp of this.ωpendingPeerConnectionTimestamps) {
939
- const peerConnection = this._peerConnections.get(pendingTimestamp.peerConnectionId);
751
+ private _updatePeerConnection(sample: PeerConnectionSample): ObservedPeerConnection | undefined {
752
+ let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId);
940
753
 
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;
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);
947
760
  }
948
761
 
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
- }
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
+ };
967
775
 
968
- if (pendingTimestamp.type === 'added') mediaTrack.added = pendingTimestamp.timestamp;
969
- else if (pendingTimestamp.type === 'removed') mediaTrack.removed = pendingTimestamp.timestamp;
970
- }
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);
971
786
 
972
- this.ωpendingMediaTrackTimestamps = newPendingTimestamps;
787
+ observedPeerConnection = newObservedPeerConnection;
973
788
  }
974
789
 
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
- }
790
+ observedPeerConnection.accept(sample);
998
791
 
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
- }
792
+ return observedPeerConnection;
793
+ }
1016
794
 
1017
- dataChannel.visited = false;
1018
- }
795
+ private _mergeInjections(sample: ClientSample): ClientSample {
796
+ if (this.closed) return sample;
1019
797
 
1020
- peerConnection.visited = false;
798
+ if (this._injections.clientEvents) {
799
+ if (!sample.clientEvents) sample.clientEvents = [];
800
+ sample.clientEvents.push(...this._injections.clientEvents);
801
+
802
+ this._injections.clientEvents = undefined;
1021
803
  }
1022
804
 
1023
- // update metrics
1024
- const wasUsingTURN = this.usingTURN;
1025
- const elapsedTimeInMs = now - this.updated;
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
+ }
1026
811
 
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;
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
- peerConnection.updateMetrics();
1054
- this.deltaInboundPacketsLost += peerConnection.deltaInboundPacketsLost;
1055
- this.deltaInboundPacketsReceived += peerConnection.deltaInboundPacketsReceived;
1056
- this.deltaOutboundPacketsSent += peerConnection.deltaOutboundPacketsSent;
1057
- this.deltaReceivedAudioBytes += peerConnection.deltaReceivedAudioBytes;
1058
- this.deltaReceivedVideoBytes += peerConnection.deltaReceivedVideoBytes;
1059
- this.deltaSentAudioBytes += peerConnection.deltaSentAudioBytes;
1060
- this.deltaSentVideoBytes += peerConnection.deltaSentVideoBytes;
1061
- this.deltaDataChannelBytesReceived += peerConnection.deltaDataChannelBytesReceived;
1062
- this.deltaDataChannelBytesSent += peerConnection.deltaDataChannelBytesSent;
1063
-
1064
- this.availableIncomingBitrate += peerConnection.availableIncomingBitrate ?? 0;
1065
- this.availableOutgoingBitrate += peerConnection.availableOutgoingBitrate ?? 0;
1066
-
1067
- sumRttInMs += peerConnection.avgRttInMs ?? 0;
1068
-
1069
- anyPeerConnectionUsingTurn ||= peerConnection.usingTURN;
1070
-
1071
- if (peerConnection.score) {
1072
- if (minPcScore === undefined || peerConnection.score.score < minPcScore) minPcScore = peerConnection.score.score;
1073
- if (maxPcScore === undefined || peerConnection.score.score > maxPcScore) maxPcScore = peerConnection.score.score;
1074
-
1075
- ++numberOfScoredPeerConnections;
1076
- }
812
+ if (this._injections.extensionStats) {
813
+ if (!sample.extensionStats) sample.extensionStats = [];
814
+ sample.extensionStats.push(...this._injections.extensionStats);
815
+
816
+ this._injections.extensionStats = undefined;
1077
817
  }
1078
818
 
1079
- this.score = {
1080
- score: minPcScore ?? -1,
1081
- remarks: [ {
1082
- severity: 'none',
1083
- text: `Min and max score of all peer connections: ${minPcScore}, ${maxPcScore}, number of PeerConnections with scores: ${numberOfScoredPeerConnections}`,
1084
- } ],
1085
- timestamp: sample.timestamp,
1086
- };
1087
- 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
+ }
1088
825
 
1089
- this.usingTURN = anyPeerConnectionUsingTurn === true;
1090
-
1091
- if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
1092
-
1093
- this.totalSentBytes += this.deltaSentAudioBytes + this.deltaSentVideoBytes;
1094
- this.totalReceivedBytes += this.deltaReceivedAudioBytes + this.deltaReceivedVideoBytes;
1095
- this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
1096
- this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
1097
- this.totalSentAudioBytes += this.deltaSentAudioBytes;
1098
- this.totalSentVideoBytes += this.deltaSentVideoBytes;
1099
- this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
1100
- this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
1101
- this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
1102
- this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
1103
- this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
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;
1104
850
 
1105
- this.outboundAudioBitrate = (this.deltaSentAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1106
- this.outboundVideoBitrate = (this.deltaSentVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1107
- this.inboundAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1108
- this.inboundVideoBitrate = (this.deltaReceivedVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1109
-
1110
- this.avgRttInMs = this._peerConnections.size ? sumRttInMs / this._peerConnections.size : undefined;
851
+ // this.totalNumberOfIssues = 0;
1111
852
 
1112
- // to make sure when sample is emitted it can be associated to this client
1113
- sample.clientId = this.clientId;
1114
-
1115
- this.updated = now;
1116
- this.lastStatsTimestamp = sample.timestamp;
1117
- this.emit('update', {
1118
- sample,
1119
- elapsedTimeInMs,
1120
- });
1121
- }
1122
-
1123
- private _addAndEmitIssue(issue: ClientIssue) {
1124
- this.issues.push(issue);
1125
-
1126
- if (issue.peerConnectionId) {
1127
- const peerConnection = this._peerConnections.get(issue.peerConnectionId);
1128
-
1129
- if (peerConnection) {
1130
-
1131
- if (issue.trackId) {
1132
- const track = peerConnection.inboundAudioTracks.get(issue.trackId) ?? peerConnection.inboundVideoTracks.get(issue.trackId);
1133
-
1134
- if (track) track.ωpendingIssuesForScores.push(issue);
1135
- } else {
1136
- peerConnection.ωpendingIssuesForScores.push(issue);
1137
- }
1138
- }
1139
- }
1140
-
1141
- this.emit('issue', issue);
1142
- }
1143
-
1144
- private _createPeerConnection(peerConnectionId: string, label?: string): ObservedPeerConnection {
1145
- const result = new ObservedPeerConnection({
1146
- peerConnectionId,
1147
- label,
1148
- }, this);
1149
-
1150
- const onNewSelectedCandidatePair = ({ localCandidate, remoteCandidate }: { localCandidate: IceLocalCandidate, remoteCandidate: IceRemoteCandidate }) => {
1151
- this.emit('selectedcandidatepair', {
1152
- peerConnection: result,
1153
- localCandidate,
1154
- remoteCandidate,
1155
- });
1156
- };
1157
-
1158
- result.once('close', () => {
1159
- result.ICE.off('new-selected-candidate-pair', onNewSelectedCandidatePair);
1160
- this._peerConnections.delete(peerConnectionId);
1161
- });
1162
- result.ICE.on('new-selected-candidate-pair', onNewSelectedCandidatePair);
1163
- this._peerConnections.set(peerConnectionId, result);
1164
-
1165
- 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,
1166
878
 
1167
- return result;
1168
- }
1169
- }
879
+ // totalScoreSum: this.totalScoreSum,
880
+ // numberOfScoreMeasurements: this.numberOfScoreMeasurements,
881
+ // };
882
+ // }
883
+ }