@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,65 +1,146 @@
1
1
  import { EventEmitter } from 'events';
2
- import { PeerConnectionTransport } from '@observertc/sample-schemas-js';
3
2
  import { ObservedClient } from './ObservedClient';
4
- import { CallEventReport, PeerConnectionTransportReport } from '@observertc/report-schemas-js';
5
- import { ObservedICE } from './ObservedICE';
3
+ import { CertificateStats, CodecStats, DataChannelStats, IceCandidateStats, InboundRtpStats, InboundTrackSample, MediaPlayoutStats, MediaSourceStats, OutboundRtpStats, OutboundTrackSample, PeerConnectionSample, PeerConnectionTransportStats, RemoteInboundRtpStats, RemoteOutboundRtpStats } from './schema/ClientSample';
4
+ import { ObservedInboundRtp } from './ObservedInboundRtp';
5
+ import { createLogger } from './common/logger';
6
+ import { MediaKind } from './common/types';
7
+ import { ObservedOutboundRtp } from './ObservedOutboundRtp';
8
+ import { ObservedCertificate } from './ObservedCertificate';
9
+ import { ObservedCodec } from './ObservedCodec';
6
10
  import { ObservedDataChannel } from './ObservedDataChannel';
7
- import { PartialBy } from './common/utils';
8
- import { ObservedInboundAudioTrack, ObservedInboundAudioTrackModel } from './ObservedInboundAudioTrack';
9
- import { ObservedInboundVideoTrack, ObservedInboundVideoTrackModel } from './ObservedInboundVideoTrack';
10
- import { ObservedOutboundAudioTrack, ObservedOutboundAudioTrackModel } from './ObservedOutboundAudioTrack';
11
- import { ObservedOutboundVideoTrack, ObservedOutboundVideoTrackModel } from './ObservedOutboundVideoTrack';
12
- import { CalculatedScore, getRttScore } from './common/CalculatedScore';
13
- import { ClientIssue } from './monitors/CallSummary';
11
+ import { ObservedIceCandidate } from './ObservedIceCandidate';
12
+ import { ObservedIceCandidatePair } from './ObservedIceCandidatePair';
13
+ import { ObservedIceTransport } from './ObservedIceTransport';
14
+ import { ObservedMediaSource } from './ObservedMediaSource';
15
+ import { ObservedPeerConnectionTransport } from './ObservedPeerConnectionTransport';
16
+ import { ObservedMediaPlayout } from './ObservedMediaPlayout';
17
+ import { ObservedRemoteInboundRtp } from './ObservedRemoteInboundRtp';
18
+ import { ObservedRemoteOutboundRtp } from './ObservedRemoteOutboundRtp';
19
+ import { ObservedInboundTrack } from './ObservedInboundTrack';
20
+ import { ObservedOutboundTrack } from './ObservedOutboundTrack';
21
+ import { CalculatedScore } from './scores/CalculatedScore';
22
+ import { ObservedTurnServer } from './ObservedTurnServer';
23
+ import { getMedian } from './common/utils';
24
+
25
+ const logger = createLogger('ObservedPeerConnection');
14
26
 
15
27
  export type ObservedPeerConnectionEvents = {
16
- update: [{
17
- elapsedTimeInMs: number;
18
- }],
19
- close: [],
20
- score: [CalculatedScore],
21
- newinboudaudiotrack: [ObservedInboundAudioTrack],
22
- newinboudvideotrack: [ObservedInboundVideoTrack],
23
- newoutboundaudiotrack: [ObservedOutboundAudioTrack],
24
- newoutboundvideotrack: [ObservedOutboundVideoTrack],
25
- newdatachannel: [ObservedDataChannel],
26
- };
27
-
28
- export type ObservedPeerConnectionModel = {
29
- peerConnectionId: string;
30
- label?: string;
28
+ iceconnectionstatechange: [
29
+ {
30
+ state: string;
31
+ }
32
+ ];
33
+ icegatheringstatechange: [
34
+ {
35
+ state: string;
36
+ }
37
+ ];
38
+ connectionstatechange: [
39
+ {
40
+ state: string;
41
+ }
42
+ ];
43
+ selectedcandidatepair: [];
44
+
45
+ 'added-certificate': [ObservedCertificate];
46
+ 'added-codec': [ObservedCodec];
47
+ 'added-data-channel': [ObservedDataChannel];
48
+ 'added-ice-candidate': [ObservedIceCandidate];
49
+ 'added-ice-candidate-pair': [ObservedIceCandidatePair];
50
+ 'added-ice-transport': [ObservedIceTransport];
51
+ 'added-inbound-rtp': [ObservedInboundRtp];
52
+ 'added-inbound-track': [ObservedInboundTrack];
53
+ 'added-media-playout': [ObservedMediaPlayout];
54
+ 'added-media-source': [ObservedMediaSource];
55
+ 'added-outbound-rtp': [ObservedOutboundRtp];
56
+ 'added-outbound-track': [ObservedOutboundTrack];
57
+ 'added-peer-connection-transport': [ObservedPeerConnectionTransport];
58
+ 'added-remote-inbound-rtp': [ObservedRemoteInboundRtp];
59
+ 'added-remote-outbound-rtp': [ObservedRemoteOutboundRtp];
60
+ 'removed-certificate': [ObservedCertificate];
61
+ 'removed-codec': [ObservedCodec];
62
+ 'removed-data-channel': [ObservedDataChannel];
63
+ 'removed-ice-candidate': [ObservedIceCandidate];
64
+ 'removed-ice-candidate-pair': [ObservedIceCandidatePair];
65
+ 'removed-ice-transport': [ObservedIceTransport];
66
+ 'removed-inbound-rtp': [ObservedInboundRtp];
67
+ 'removed-inbound-track': [ObservedInboundTrack];
68
+ 'removed-media-playout': [ObservedMediaPlayout];
69
+ 'removed-media-source': [ObservedMediaSource];
70
+ 'removed-outbound-rtp': [ObservedOutboundRtp];
71
+ 'removed-outbound-track': [ObservedOutboundTrack];
72
+ 'removed-peer-connection-transport': [ObservedPeerConnectionTransport];
73
+ 'removed-remote-inbound-rtp': [ObservedRemoteInboundRtp];
74
+ 'removed-remote-outbound-rtp': [ObservedRemoteOutboundRtp];
75
+ 'updated-inbound-rtp': [ObservedInboundRtp];
76
+ 'updated-outbound-rtp': [ObservedOutboundRtp];
77
+ 'updated-inbound-track': [ObservedInboundTrack];
78
+ 'updated-outbound-track': [ObservedOutboundTrack];
79
+ 'updated-ice-candidate-pair': [ObservedIceCandidatePair];
80
+ 'updated-ice-transport': [ObservedIceTransport];
81
+ 'updated-peer-connection-transport': [ObservedPeerConnectionTransport];
82
+ 'updated-media-source': [ObservedMediaSource];
83
+ 'updated-media-playout': [ObservedMediaPlayout];
84
+ 'updated-data-channel': [ObservedDataChannel];
85
+ 'updated-ice-candidate': [ObservedIceCandidate];
86
+ 'updated-certificate': [ObservedCertificate];
87
+ 'updated-codec': [ObservedCodec];
88
+ 'updated-remote-inbound-rtp': [ObservedRemoteInboundRtp];
89
+ 'updated-remote-outbound-rtp': [ObservedRemoteOutboundRtp];
90
+ 'muted-inbound-track': [ObservedInboundTrack];
91
+ 'muted-outbound-track': [ObservedOutboundTrack];
92
+ 'unmuted-inbound-track': [ObservedInboundTrack];
93
+ 'unmuted-outbound-track': [ObservedOutboundTrack];
94
+
95
+ 'update': [],
96
+ close: [];
31
97
  };
32
98
 
33
- export type ObservedPeerConnectionStats = Omit<PeerConnectionTransport, 'transportId' | 'label'>;
34
-
35
99
  export declare interface ObservedPeerConnection {
36
- on<U extends keyof ObservedPeerConnectionEvents>(event: U, listener: (...args: ObservedPeerConnectionEvents[U]) => void): this;
37
- off<U extends keyof ObservedPeerConnectionEvents>(event: U, listener: (...args: ObservedPeerConnectionEvents[U]) => void): this;
38
- once<U extends keyof ObservedPeerConnectionEvents>(event: U, listener: (...args: ObservedPeerConnectionEvents[U]) => void): this;
100
+ on<U extends keyof ObservedPeerConnectionEvents>(
101
+ event: U,
102
+ listener: (...args: ObservedPeerConnectionEvents[U]) => void
103
+ ): this;
104
+ off<U extends keyof ObservedPeerConnectionEvents>(
105
+ event: U,
106
+ listener: (...args: ObservedPeerConnectionEvents[U]) => void
107
+ ): this;
108
+ once<U extends keyof ObservedPeerConnectionEvents>(
109
+ event: U,
110
+ listener: (...args: ObservedPeerConnectionEvents[U]) => void
111
+ ): this;
39
112
  emit<U extends keyof ObservedPeerConnectionEvents>(event: U, ...args: ObservedPeerConnectionEvents[U]): boolean;
40
113
  }
41
114
 
42
115
  export class ObservedPeerConnection extends EventEmitter {
43
- public readonly created = Date.now();
44
- public visited = true;
116
+ private _visited = true;
45
117
 
118
+ public appData?: Record<string, unknown>;
119
+ public readonly calculatedScore: CalculatedScore = {
120
+ weight: 1,
121
+ value: undefined,
122
+ };
123
+
124
+ public closed = false;
46
125
  // timestamp of the PEER_CONNECTION_OPENED event
47
- public opened?: number;
126
+ public openedAt?: number;
48
127
  // timestamp of the PEER_CONNECTION_CLOSED event
49
- public closedTimestamp?: number;
50
-
51
- private _elapsedTimeSinceLastUpdate?: number;
52
- private _statsTimestamp?: number;
53
- private _stabilityScores: number[] = [];
128
+ public closedAt?: number;
129
+ public updated = Date.now();
54
130
 
55
- public ωpendingIssuesForScores: ClientIssue[] = [];
131
+ public connectionState?: 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';
132
+ public iceConnectionState?: 'new' | 'checking' | 'connected' | 'completed' | 'failed' | 'disconnected' | 'closed';
133
+ public iceGatheringState?: 'new' | 'gathering' | 'complete';
56
134
 
57
- public score?: CalculatedScore;
135
+ public availableIncomingBitrate = 0;
136
+ public availableOutgoingBitrate = 0;
58
137
  public totalInboundPacketsLost = 0;
59
138
  public totalInboundPacketsReceived = 0;
60
139
  public totalOutboundPacketsSent = 0;
61
140
  public totalDataChannelBytesSent = 0;
62
141
  public totalDataChannelBytesReceived = 0;
142
+ public totalDataChannelMessagesSent = 0;
143
+ public totalDataChannelMessagesReceived = 0;
63
144
 
64
145
  public totalSentAudioBytes = 0;
65
146
  public totalSentVideoBytes = 0;
@@ -75,182 +156,181 @@ export class ObservedPeerConnection extends EventEmitter {
75
156
  public deltaOutboundPacketsSent = 0;
76
157
  public deltaDataChannelBytesSent = 0;
77
158
  public deltaDataChannelBytesReceived = 0;
159
+ public deltaDataChannelMessagesSent = 0;
160
+ public deltaDataChannelMessagesReceived = 0;
78
161
  public deltaInboundReceivedBytes = 0;
79
162
  public deltaOutboundSentBytes = 0;
80
-
163
+
81
164
  public deltaReceivedAudioBytes = 0;
82
165
  public deltaReceivedVideoBytes = 0;
83
166
  public deltaReceivedAudioPackets = 0;
84
167
  public deltaReceivedVideoPackets = 0;
85
168
  public deltaSentAudioBytes = 0;
86
169
  public deltaSentVideoBytes = 0;
170
+ public deltaTransportSentBytes = 0;
171
+ public deltaTransportReceivedBytes = 0;
172
+
87
173
  public receivingPacketsPerSecond = 0;
88
174
  public sendingPacketsPerSecond = 0;
89
175
  public sendingAudioBitrate = 0;
90
176
  public sendingVideoBitrate = 0;
91
177
  public receivingAudioBitrate = 0;
92
178
  public receivingVideoBitrate = 0;
93
-
94
- public avgRttInMs?: number;
95
- public avgJitter?: number;
96
-
97
- private _closed = false;
98
- private _updated = Date.now();
99
- private _sample?: ObservedPeerConnectionStats;
100
- private _marker?: string;
101
-
102
- public readonly ICE = ObservedICE.create(this);
103
- private readonly _inboundAudioTracks = new Map<string, ObservedInboundAudioTrack>();
104
- private readonly _inboundVideoTracks = new Map<string, ObservedInboundVideoTrack>();
105
- private readonly _outboundAudioTracks = new Map<string, ObservedOutboundAudioTrack>();
106
- private readonly _outboundVideoTracks = new Map<string, ObservedOutboundVideoTrack>();
107
- private readonly _dataChannels = new Map<number, ObservedDataChannel>();
108
-
109
- public constructor(
110
- private readonly _model: ObservedPeerConnectionModel,
111
- public readonly client: ObservedClient,
112
- ) {
179
+
180
+ public currentRttInMs?: number;
181
+ public currentJitter?: number;
182
+
183
+ public usingTCP = false;
184
+ public usingTURN = false;
185
+
186
+ public observedTurnServer?: ObservedTurnServer;
187
+ public readonly observedCertificates = new Map<string, ObservedCertificate>();
188
+ public readonly observedCodecs = new Map<string, ObservedCodec>();
189
+ public readonly observedDataChannels = new Map<string, ObservedDataChannel>();
190
+ public readonly observedIceCandidates = new Map<string, ObservedIceCandidate>();
191
+ public readonly observedIceCandidatesPair = new Map<string, ObservedIceCandidatePair>();
192
+ public readonly observedIceTransports = new Map<string, ObservedIceTransport>();
193
+ public readonly observedInboundRtps = new Map<number, ObservedInboundRtp>();
194
+ public readonly observedInboundTracks = new Map<string, ObservedInboundTrack>();
195
+ public readonly observedMediaPlayouts = new Map<string, ObservedMediaPlayout>();
196
+ public readonly observedMediaSources = new Map<string, ObservedMediaSource>();
197
+ public readonly observedOutboundRtps = new Map<number, ObservedOutboundRtp>();
198
+ public readonly observedOutboundTracks = new Map<string, ObservedOutboundTrack>();
199
+ public readonly observedPeerConnectionTransports = new Map<string, ObservedPeerConnectionTransport>();
200
+ public readonly observedRemoteInboundRtps = new Map<number, ObservedRemoteInboundRtp>();
201
+ public readonly observedRemoteOutboundRtps = new Map<number, ObservedRemoteOutboundRtp>();
202
+
203
+ public constructor(public readonly peerConnectionId: string, public readonly client: ObservedClient) {
113
204
  super();
114
205
  this.setMaxListeners(Infinity);
115
206
  }
116
207
 
117
- public get serviceId() {
118
- return this.client.serviceId;
119
- }
120
-
121
- public get roomId() {
122
- return this.client.roomId;
123
- }
124
-
125
- public get callId() {
126
- return this.client.callId;
127
- }
128
-
129
- public get clientId() {
130
- return this.client.clientId;
131
- }
132
-
133
- public get mediaUnitId() {
134
- return this.client.mediaUnitId;
135
- }
136
-
137
- public get label() {
138
- return this._model.label;
208
+ public get score() {
209
+ return this.calculatedScore.value;
139
210
  }
140
211
 
141
- public set label(value: string | undefined) {
142
- this._model.label = value;
143
- }
212
+ public get visited() {
213
+ const visited = this._visited;
144
214
 
145
- public get usingTURN() {
146
- return this.ICE.usingTURN;
147
- }
215
+ this._visited = false;
148
216
 
149
- public get availableOutgoingBitrate() {
150
- return this.ICE.stats?.availableOutgoingBitrate;
217
+ return visited;
151
218
  }
152
219
 
153
- public get availableIncomingBitrate() {
154
- return this.ICE.stats?.availableIncomingBitrate;
220
+ public get codecs() {
221
+ return [ ...this.observedCodecs.values() ];
155
222
  }
156
223
 
157
- public get peerConnectionId(): string {
158
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
159
- return this._model.peerConnectionId!;
224
+ public get inboundRtps() {
225
+ return [ ...this.observedInboundRtps.values() ];
160
226
  }
161
227
 
162
- public get reports() {
163
- return this.client.reports;
228
+ public get remoteOutboundRtps() {
229
+ return [ ...this.observedRemoteOutboundRtps.values() ];
164
230
  }
165
231
 
166
- public get stats(): ObservedPeerConnectionStats | undefined {
167
- return this._sample;
232
+ public get outboundRtps() {
233
+ return [ ...this.observedOutboundRtps.values() ];
168
234
  }
169
235
 
170
- public get updated(): number {
171
- return this._updated;
236
+ public get remoteInboundRtps() {
237
+ return [ ...this.observedRemoteInboundRtps.values() ];
172
238
  }
173
239
 
174
- public get inboundAudioTracks(): ReadonlyMap<string, ObservedInboundAudioTrack> {
175
- return this._inboundAudioTracks;
240
+ public get mediaSources() {
241
+ return [ ...this.observedMediaSources.values() ];
176
242
  }
177
243
 
178
- public get inboundVideoTracks(): ReadonlyMap<string, ObservedInboundVideoTrack> {
179
- return this._inboundVideoTracks;
244
+ public get mediaPlayouts() {
245
+ return [ ...this.observedMediaPlayouts.values() ];
180
246
  }
181
247
 
182
- public get outboundAudioTracks(): ReadonlyMap<string, ObservedOutboundAudioTrack> {
183
- return this._outboundAudioTracks;
248
+ public get dataChannels() {
249
+ return [ ...this.observedDataChannels.values() ];
184
250
  }
185
251
 
186
- public get outboundVideoTracks(): ReadonlyMap<string, ObservedOutboundVideoTrack> {
187
- return this._outboundVideoTracks;
252
+ public get peerConnectionTransports() {
253
+ return [ ...this.observedPeerConnectionTransports.values() ];
188
254
  }
189
255
 
190
- public get dataChannels(): ReadonlyMap<number, ObservedDataChannel> {
191
- return this._dataChannels;
256
+ public get iceTransports() {
257
+ return [ ...this.observedIceTransports.values() ];
192
258
  }
193
259
 
194
- public get uptimeInMs() {
195
- return this._updated - this.created;
260
+ public get iceCandidates() {
261
+ return [ ...this.observedIceCandidates.values() ];
196
262
  }
197
263
 
198
- public get marker() {
199
- return this._marker;
264
+ public get iceCandidatePairs() {
265
+ return [ ...this.observedIceCandidatesPair.values() ];
200
266
  }
201
267
 
202
- public set marker(value: string | undefined) {
203
- this._marker = value;
204
- this._inboundAudioTracks.forEach((track) => (track.marker = value));
205
- this._inboundVideoTracks.forEach((track) => (track.marker = value));
206
- this._outboundAudioTracks.forEach((track) => (track.marker = value));
207
- this._outboundVideoTracks.forEach((track) => (track.marker = value));
208
- this._dataChannels.forEach((channel) => (channel.marker = value));
209
- this.ICE.marker = value;
268
+ public get certificates() {
269
+ return [ ...this.observedCertificates.values() ];
210
270
  }
211
271
 
212
- public addEventReport(params: PartialBy<Omit<CallEventReport, 'serviceId' | 'roomId' | 'callId' | 'clientId' | 'userId' | 'peerConnectionId' | 'marker'>, 'timestamp'>) {
213
- this.reports.addCallEventReport({
214
- ...params,
215
- serviceId: this.serviceId,
216
- mediaUnitId: this.mediaUnitId,
217
- roomId: this.roomId,
218
- callId: this.callId,
219
- clientId: this.clientId,
220
- userId: this.client.userId,
221
- peerConnectionId: this.peerConnectionId,
222
- marker: this.client.marker,
223
- timestamp: params.timestamp ?? Date.now(),
224
- });
272
+ public get selectedIceCandidatePairs() {
273
+ return this.iceTransports.map((iceTransport) => iceTransport.getSelectedCandidatePair())
274
+ .filter((pair) => pair !== undefined) as ObservedIceCandidatePair[];
225
275
  }
226
276
 
227
- public get closed() {
228
- return this._closed;
277
+ public get selectedIceCandiadtePairForTurn() {
278
+ return this.selectedIceCandidatePairs
279
+ .filter((pair) =>
280
+ pair.getLocalCandidate()?.candidateType === 'relay' &&
281
+ pair.getRemoteCandidate()?.url?.startsWith('turn:')
282
+ );
229
283
  }
230
284
 
231
285
  public close() {
232
- if (this._closed) return;
233
- this._closed = true;
234
-
235
- Array.from(this._inboundAudioTracks.values()).forEach((track) => track.close());
236
- Array.from(this._inboundVideoTracks.values()).forEach((track) => track.close());
237
- Array.from(this._outboundAudioTracks.values()).forEach((track) => {
238
- this.client.call.sfuStreamIdToOutboundAudioTrack.delete(track.sfuStreamId ?? '');
239
- track.close();
240
- });
241
- Array.from(this._outboundVideoTracks.values()).forEach((track) => {
242
- this.client.call.sfuStreamIdToOutboundVideoTrack.delete(track.sfuStreamId ?? '');
243
- track.close();
244
- });
245
-
286
+ if (this.closed) return;
287
+ this.closed = true;
288
+
289
+ this.observedCertificates.forEach((cert) => this.emit('removed-certificate', cert));
290
+ this.observedCodecs.forEach((codec) => this.emit('removed-codec', codec));
291
+ this.observedDataChannels.forEach((dc) => this.emit('removed-data-channel', dc));
292
+ this.observedIceCandidates.forEach((candidate) => this.emit('removed-ice-candidate', candidate));
293
+ this.observedIceCandidatesPair.forEach((pair) => this.emit('removed-ice-candidate-pair', pair));
294
+ this.observedIceTransports.forEach((transport) => this.emit('removed-ice-transport', transport));
295
+ this.observedInboundRtps.forEach((rtp) => this.emit('removed-inbound-rtp', rtp));
296
+ this.observedInboundTracks.forEach((track) => this.emit('removed-inbound-track', track));
297
+ this.observedMediaPlayouts.forEach((playout) => this.emit('removed-media-playout', playout));
298
+ this.observedMediaSources.forEach((source) => this.emit('removed-media-source', source));
299
+ this.observedOutboundRtps.forEach((rtp) => this.emit('removed-outbound-rtp', rtp));
300
+ this.observedOutboundTracks.forEach((track) => this.emit('removed-outbound-track', track));
301
+ this.observedPeerConnectionTransports.forEach((transport) => this.emit('removed-peer-connection-transport', transport));
302
+ this.observedRemoteInboundRtps.forEach((rtp) => this.emit('removed-remote-inbound-rtp', rtp));
303
+ this.observedRemoteOutboundRtps.forEach((rtp) => this.emit('removed-remote-outbound-rtp', rtp));
304
+
305
+ this.observedCertificates.clear();
306
+ this.observedCodecs.clear();
307
+ this.observedDataChannels.clear();
308
+ this.observedIceCandidates.clear();
309
+ this.observedIceCandidatesPair.clear();
310
+ this.observedIceTransports.clear();
311
+ this.observedInboundRtps.clear();
312
+ this.observedInboundTracks.clear();
313
+ this.observedMediaPlayouts.clear();
314
+ this.observedMediaSources.clear();
315
+ this.observedOutboundRtps.clear();
316
+ this.observedOutboundTracks.clear();
317
+ this.observedPeerConnectionTransports.clear();
318
+ this.observedRemoteInboundRtps.clear();
319
+ this.observedRemoteOutboundRtps.clear();
320
+
321
+ this.client.call.observer.observedTURN.removePeerConnection(this);
322
+
323
+ if (!this.closedAt) this.closedAt = Date.now();
324
+
246
325
  this.emit('close');
247
326
  }
248
327
 
249
- public getTrack(trackId: string): ObservedInboundAudioTrack | ObservedInboundVideoTrack | ObservedOutboundAudioTrack | ObservedOutboundVideoTrack | undefined {
250
- return this._inboundAudioTracks.get(trackId) ?? this._inboundVideoTracks.get(trackId) ?? this._outboundAudioTracks.get(trackId) ?? this._outboundVideoTracks.get(trackId);
251
- }
328
+ public accept(sample: PeerConnectionSample) {
329
+ if (this.closed) return;
330
+ this._visited = true;
252
331
 
253
- public resetMetrics() {
332
+ this.availableIncomingBitrate = 0;
333
+ this.availableOutgoingBitrate = 0;
254
334
  this.deltaInboundPacketsLost = 0;
255
335
  this.deltaInboundPacketsReceived = 0;
256
336
  this.deltaOutboundPacketsSent = 0;
@@ -258,316 +338,767 @@ export class ObservedPeerConnection extends EventEmitter {
258
338
  this.deltaDataChannelBytesReceived = 0;
259
339
  this.deltaInboundReceivedBytes = 0;
260
340
  this.deltaOutboundSentBytes = 0;
261
-
262
341
  this.deltaReceivedAudioBytes = 0;
263
342
  this.deltaReceivedVideoBytes = 0;
264
343
  this.deltaReceivedAudioPackets = 0;
265
344
  this.deltaReceivedVideoPackets = 0;
266
345
  this.deltaSentAudioBytes = 0;
267
346
  this.deltaSentVideoBytes = 0;
347
+ this.deltaTransportReceivedBytes = 0;
348
+ this.deltaTransportSentBytes = 0;
349
+
268
350
  this.sendingAudioBitrate = 0;
269
351
  this.sendingVideoBitrate = 0;
270
352
  this.receivingAudioBitrate = 0;
271
353
  this.receivingVideoBitrate = 0;
272
354
 
273
- this.ICE.resetMetrics();
355
+ const now = Date.now();
356
+ const elapsedTimeInMs = now - this.updated;
357
+ const elapsedTimeInSec = elapsedTimeInMs / 1000;
358
+ const rttMeasurementsInSec: number[] = [];
359
+ const jitterMeasurements: number[] = [];
360
+
361
+ if (sample.certificates) {
362
+ for (const certificate of sample.certificates) {
363
+ this._updateCertificateStats(certificate);
364
+ }
365
+ }
366
+ if (sample.codecs) {
367
+ for (const codec of sample.codecs) {
368
+ this._updateCodecStats(codec);
369
+ }
370
+ }
371
+ if (sample.dataChannels) {
372
+ for (const dataChannel of sample.dataChannels) {
373
+ const observedDataChannel = this._updateDataChannelStats(dataChannel);
374
+
375
+ if (!observedDataChannel) continue;
376
+
377
+ this.deltaDataChannelBytesSent += observedDataChannel.deltaBytesSent;
378
+ this.deltaDataChannelBytesReceived += observedDataChannel.deltaBytesReceived;
379
+ this.deltaDataChannelMessagesSent += observedDataChannel.deltaMessagesSent;
380
+ this.deltaDataChannelMessagesReceived += observedDataChannel.deltaMessagesReceived;
381
+ }
382
+ }
383
+ if (sample.iceCandidates) {
384
+ for (const iceCandidate of sample.iceCandidates) {
385
+ this._updateIceCandidateStats(iceCandidate);
386
+ }
387
+ }
388
+ if (sample.iceCandidatePairs) {
389
+ for (const iceCandidatePair of sample.iceCandidatePairs) {
390
+ const observedCandidatePair = this._updateIceCandidatePairStats(iceCandidatePair);
391
+
392
+ if (!observedCandidatePair) continue;
393
+
394
+ if (observedCandidatePair.currentRoundTripTime) {
395
+ rttMeasurementsInSec.push(observedCandidatePair.currentRoundTripTime);
396
+ }
397
+ if (observedCandidatePair.availableIncomingBitrate) {
398
+ this.availableIncomingBitrate += observedCandidatePair.availableIncomingBitrate;
399
+ }
400
+ if (observedCandidatePair.availableOutgoingBitrate) {
401
+ this.availableOutgoingBitrate += observedCandidatePair.availableOutgoingBitrate;
402
+ }
403
+ }
404
+ }
405
+ if (sample.iceTransports) {
406
+ for (const iceTransport of sample.iceTransports) {
407
+ const observedIceTransport = this._updateIceTransportStats(iceTransport);
408
+
409
+ if (!observedIceTransport) return;
410
+
411
+ observedIceTransport.bytesReceived;
412
+
413
+ }
414
+ }
415
+ if (sample.inboundRtps) {
416
+ for (const inboundRtp of sample.inboundRtps) {
417
+ const observedInboundRtp = this._updateInboundRtpStats(inboundRtp);
418
+
419
+ if (!observedInboundRtp) continue;
420
+
421
+ this.deltaInboundPacketsLost += observedInboundRtp.deltaLostPackets;
422
+ this.deltaInboundPacketsReceived += observedInboundRtp.deltaReceivedPackets;
423
+ this.deltaInboundReceivedBytes += observedInboundRtp.deltaBytesReceived;
424
+
425
+ switch (inboundRtp.kind) {
426
+ case 'audio':
427
+ this.deltaReceivedAudioBytes += observedInboundRtp.deltaBytesReceived;
428
+ this.deltaReceivedAudioPackets += observedInboundRtp.deltaReceivedPackets;
429
+ break;
430
+ case 'video':
431
+ this.deltaReceivedVideoBytes += observedInboundRtp.deltaBytesReceived;
432
+ this.deltaReceivedVideoPackets += observedInboundRtp.deltaReceivedPackets;
433
+ break;
434
+ }
435
+
436
+ if (observedInboundRtp.jitter) {
437
+ jitterMeasurements.push(observedInboundRtp.jitter);
438
+ }
439
+ }
440
+ }
441
+ if (sample.mediaPlayouts) {
442
+ for (const mediaPlayout of sample.mediaPlayouts) {
443
+ this._updateMediaPlayoutStats(mediaPlayout);
444
+ }
445
+ }
446
+ if (sample.mediaSources) {
447
+ for (const mediaSource of sample.mediaSources) {
448
+ this._updateMediaSourceStats(mediaSource);
449
+ }
450
+ }
451
+ if (sample.outboundRtps) {
452
+ for (const outboundRtp of sample.outboundRtps) {
453
+ const observedOutboundRtp = this._updateOutboundRtpStats(outboundRtp);
454
+
455
+ if (!observedOutboundRtp) continue;
456
+
457
+ this.deltaOutboundPacketsSent += observedOutboundRtp.deltaPacketsSent ?? 0;
458
+ this.deltaOutboundSentBytes += observedOutboundRtp.deltaBytesSent ?? 0;
459
+
460
+ switch (outboundRtp.kind) {
461
+ case 'audio':
462
+ this.deltaSentAudioBytes += observedOutboundRtp.deltaBytesSent;
463
+ this.deltaSentAudioBytes += observedOutboundRtp.deltaPacketsSent;
464
+ break;
465
+ case 'video':
466
+ this.deltaSentVideoBytes += observedOutboundRtp.deltaBytesSent;
467
+ this.deltaSentVideoBytes += observedOutboundRtp.deltaPacketsSent;
468
+ break;
469
+ }
470
+
471
+ }
472
+ }
473
+ if (sample.peerConnectionTransports) {
474
+ for (const peerConnectionTransport of sample.peerConnectionTransports) {
475
+ const observedTransport = this._updatePeerConnectionTransportStats(peerConnectionTransport);
476
+
477
+ if (!observedTransport) continue;
478
+
479
+ }
480
+ }
481
+ if (sample.remoteInboundRtps) {
482
+ for (const remoteInboundRtp of sample.remoteInboundRtps) {
483
+ const observedRemoteInboundRtp = this._updateRemoteInboundRtpStats(remoteInboundRtp);
484
+
485
+ if (!observedRemoteInboundRtp) continue;
486
+
487
+ if (observedRemoteInboundRtp.roundTripTime) {
488
+ rttMeasurementsInSec.push(observedRemoteInboundRtp.roundTripTime);
489
+ }
490
+ }
491
+ }
492
+ if (sample.remoteOutboundRtps) {
493
+ for (const remoteOutboundRtp of sample.remoteOutboundRtps) {
494
+ const observedRemoteOutboundRtp = this._updateRemoteOutboundRtpStats(remoteOutboundRtp);
495
+
496
+ if (!observedRemoteOutboundRtp) continue;
497
+ }
498
+ }
499
+
500
+ // tracks should be updated last as they are derived stats
501
+ // and depends on base stats but they all received in the sample sample
502
+ if (sample.inboundTracks) {
503
+ for (const inboundTrack of sample.inboundTracks) {
504
+ this._updateInboundTrackSample(inboundTrack);
505
+ }
506
+ }
507
+ if (sample.outboundTracks) {
508
+ for (const outboundTrack of sample.outboundTracks) {
509
+ this._updateOutboundTrackSample(outboundTrack);
510
+ }
511
+ }
512
+
513
+ this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
514
+ this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
515
+ this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
516
+ this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
517
+ this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
518
+ this.totalDataChannelMessagesSent += this.deltaDataChannelMessagesSent;
519
+ this.totalDataChannelMessagesReceived += this.deltaDataChannelMessagesReceived;
520
+ this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
521
+ this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
522
+ this.totalSentAudioBytes += this.deltaSentAudioBytes;
523
+ this.totalSentVideoBytes += this.deltaSentVideoBytes;
524
+ this.totalReceivedAudioPacktes += this.deltaReceivedAudioPackets;
525
+ this.totalReceivedVideoPackets += this.deltaReceivedVideoPackets;
526
+ this.totalSentAudioPackets += this.deltaSentAudioBytes;
527
+ this.totalSentVideoPackets += this.deltaSentVideoBytes;
528
+
529
+ this.receivingPacketsPerSecond = this.deltaInboundPacketsReceived / elapsedTimeInSec;
530
+ this.sendingPacketsPerSecond = this.deltaOutboundPacketsSent / elapsedTimeInSec;
531
+ this.sendingAudioBitrate = (this.deltaSentAudioBytes * 8) / elapsedTimeInSec;
532
+ this.sendingVideoBitrate = (this.deltaSentVideoBytes * 8) / elapsedTimeInSec;
533
+ this.receivingAudioBitrate = (this.deltaReceivedAudioBytes * 8) / elapsedTimeInSec;
534
+ this.receivingVideoBitrate = (this.deltaReceivedVideoBytes * 8) / elapsedTimeInSec;
535
+
536
+ if (rttMeasurementsInSec.length > 0) {
537
+ this.currentRttInMs = getMedian(rttMeasurementsInSec, false) * 1000;
538
+ } else {
539
+ this.currentRttInMs = undefined;
540
+ }
541
+ if (jitterMeasurements.length > 0) {
542
+ this.currentJitter = getMedian(jitterMeasurements, false);
543
+ } else {
544
+ this.currentJitter = undefined;
545
+ }
546
+ const wasUsingTURN = this.usingTURN;
547
+ const selectedIceCandidatePairs = this.selectedIceCandidatePairs;
548
+ const selectedCandidatePairForTurn: ObservedIceCandidatePair[] = [];
549
+
550
+ this.usingTCP = false;
551
+ this.usingTURN = false;
552
+
553
+ for (const selectedCandidatePair of selectedIceCandidatePairs) {
554
+ if (selectedCandidatePair.getLocalCandidate()?.protocol === 'tcp') {
555
+ this.usingTCP = true;
556
+ }
557
+ if (selectedCandidatePair.getLocalCandidate()?.candidateType === 'relay' && selectedCandidatePair.getRemoteCandidate()?.url?.startsWith('turn:')) {
558
+ selectedCandidatePairForTurn.push(selectedCandidatePair);
559
+ this.usingTURN = true;
560
+ }
561
+ this.deltaTransportReceivedBytes += selectedCandidatePair.deltaBytesReceived;
562
+ this.deltaTransportSentBytes += selectedCandidatePair.deltaBytesSent;
563
+ }
564
+
565
+ if (this.usingTURN) {
566
+ if (!this.observedTurnServer) {
567
+ this.observedTurnServer = this.client.call.observer.observedTURN.addPeerConnection(this);
568
+ }
569
+ this.observedTurnServer?.updateTurnUsage(...selectedCandidatePairForTurn);
570
+ } else if (wasUsingTURN) {
571
+ if (!this.usingTURN) {
572
+ this.client.call.observer.observedTURN.removePeerConnection(this);
573
+ }
574
+ }
575
+ this.calculatedScore.value = sample.score;
576
+ this.updated = now;
577
+ this._checkVisited();
578
+
579
+ this.emit('update');
274
580
  }
275
581
 
276
- public update(sample: PeerConnectionTransport, timestamp: number) {
277
- if (this._closed) return;
278
- if (sample.peerConnectionId !== this._model.peerConnectionId) throw new Error(`TransportId mismatch. PeerConnectionId: ${ this._model.peerConnectionId } TransportId: ${ sample.transportId}`);
582
+ private _checkVisited() {
583
+ for (const certificate of [ ...this.observedCertificates.values() ]) {
584
+ if (certificate.visited) continue;
279
585
 
280
- this._sample = sample;
281
- if (this._model.label !== sample.label) {
282
- this._model.label = sample.label;
586
+ this.observedCertificates.delete(certificate.id);
283
587
  }
284
- const now = Date.now();
285
- const report: PeerConnectionTransportReport = {
286
- serviceId: this.client.call.serviceId,
287
- roomId: this.client.call.roomId,
288
- callId: this.client.call.callId,
289
- clientId: this.client.clientId,
290
- userId: this.client.userId,
291
- mediaUnitId: this.client.mediaUnitId,
292
- ...sample,
293
- timestamp,
294
- sampleSeq: -1, // deprecated
295
- marker: this.marker,
296
- };
297
-
298
- this.reports.addPeerConnectionTransportReports(report);
299
- this._elapsedTimeSinceLastUpdate = now - this._updated;
300
- this.visited = true;
301
- this._updated = now;
302
- this._statsTimestamp = timestamp;
303
- this.emit('update', {
304
- elapsedTimeInMs: this._elapsedTimeSinceLastUpdate,
305
- });
306
- }
307
-
308
- public createInboundAudioTrack(config: ObservedInboundAudioTrackModel): ObservedInboundAudioTrack {
309
- if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
310
-
311
- const result = new ObservedInboundAudioTrack(config, this);
588
+ for (const codec of [ ...this.observedCodecs.values() ]) {
589
+ if (codec.visited) continue;
590
+
591
+ this.observedCodecs.delete(codec.id);
592
+ this.emit('removed-codec', codec);
593
+ }
594
+ for (const dataChannel of [ ...this.observedDataChannels.values() ]) {
595
+ if (dataChannel.visited) continue;
596
+
597
+ this.observedDataChannels.delete(dataChannel.id);
598
+ this.emit('removed-data-channel', dataChannel);
599
+ }
600
+ for (const iceCandidate of [ ...this.observedIceCandidates.values() ]) {
601
+ if (iceCandidate.visited) continue;
602
+
603
+ this.observedIceCandidates.delete(iceCandidate.id);
604
+ this.emit('removed-ice-candidate', iceCandidate);
605
+ }
606
+ for (const iceCandidatePair of [ ...this.observedIceCandidatesPair.values() ]) {
607
+ if (iceCandidatePair.visited) continue;
608
+
609
+ this.observedIceCandidatesPair.delete(iceCandidatePair.id);
610
+ this.emit('removed-ice-candidate-pair', iceCandidatePair);
611
+ }
612
+ for (const iceTransport of [ ...this.observedIceTransports.values() ]) {
613
+ if (iceTransport.visited) continue;
614
+
615
+ this.observedIceTransports.delete(iceTransport.id);
616
+ this.emit('removed-ice-transport', iceTransport);
617
+ }
618
+ for (const inboundRtp of [ ...this.observedInboundRtps.values() ]) {
619
+ if (inboundRtp.visited) continue;
620
+
621
+ this.observedInboundRtps.delete(inboundRtp.ssrc);
622
+ this.emit('removed-inbound-rtp', inboundRtp);
623
+ }
624
+ for (const inboundTrack of [ ...this.observedInboundTracks.values() ]) {
625
+ if (inboundTrack.visited) continue;
626
+
627
+ this.observedInboundTracks.delete(inboundTrack.id);
628
+ this.emit('removed-inbound-track', inboundTrack);
629
+ }
630
+ for (const mediaPlayout of [ ...this.observedMediaPlayouts.values() ]) {
631
+ if (mediaPlayout.visited) continue;
632
+
633
+ this.observedMediaPlayouts.delete(mediaPlayout.id);
634
+ this.emit('removed-media-playout', mediaPlayout);
635
+ }
636
+ for (const mediaSource of [ ...this.observedMediaSources.values() ]) {
637
+ if (mediaSource.visited) continue;
638
+
639
+ this.observedMediaSources.delete(mediaSource.id);
640
+ this.emit('removed-media-source', mediaSource);
641
+ }
642
+ for (const outboundRtp of [ ...this.observedOutboundRtps.values() ]) {
643
+ if (outboundRtp.visited) continue;
644
+
645
+ this.observedOutboundRtps.delete(outboundRtp.ssrc);
646
+ this.emit('removed-outbound-rtp', outboundRtp);
647
+ }
648
+ for (const outboundTrack of [ ...this.observedOutboundTracks.values() ]) {
649
+ if (outboundTrack.visited) continue;
312
650
 
313
- result.on('close', () => {
314
- this._inboundAudioTracks.delete(result.trackId);
315
- });
316
- this._inboundAudioTracks.set(result.trackId, result);
651
+ this.observedOutboundTracks.delete(outboundTrack.id);
652
+ this.emit('removed-outbound-track', outboundTrack);
653
+ }
654
+ for (const peerConnectionTransport of [ ...this.observedPeerConnectionTransports.values() ]) {
655
+ if (peerConnectionTransport.visited) continue;
656
+
657
+ this.observedPeerConnectionTransports.delete(peerConnectionTransport.id);
658
+ this.emit('removed-peer-connection-transport', peerConnectionTransport);
659
+ }
660
+ for (const remoteInboundRtp of [ ...this.observedRemoteInboundRtps.values() ]) {
661
+ if (remoteInboundRtp.visited) continue;
317
662
 
318
- this.emit('newinboudaudiotrack', result);
663
+ this.observedRemoteInboundRtps.delete(remoteInboundRtp.ssrc);
664
+ this.emit('removed-remote-inbound-rtp', remoteInboundRtp);
665
+ }
666
+ for (const remoteOutboundRtp of [ ...this.observedRemoteOutboundRtps.values() ]) {
667
+ if (remoteOutboundRtp.visited) continue;
319
668
 
320
- return result;
669
+ this.observedRemoteOutboundRtps.delete(remoteOutboundRtp.ssrc);
670
+ this.emit('removed-remote-outbound-rtp', remoteOutboundRtp);
671
+ }
321
672
  }
322
673
 
323
- public createInboundVideoTrack(config: ObservedInboundVideoTrackModel): ObservedInboundVideoTrack {
324
- if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
325
-
326
- const result = new ObservedInboundVideoTrack(config, this);
674
+ private _updateCertificateStats(stats: CertificateStats) {
675
+ let observedCertificate = this.observedCertificates.get(stats.id);
676
+
677
+ if (!observedCertificate) {
678
+ if (!stats.timestamp || !stats.id || !stats.fingerprint) {
679
+ return logger.warn(
680
+ `ObservedPeerConnection received an invalid CertificateStats (missing timestamp OR id OR fingerprint field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
681
+ stats
682
+ );
683
+ }
327
684
 
328
- result.on('close', () => {
329
- this._inboundVideoTracks.delete(result.trackId);
330
- });
331
- this._inboundVideoTracks.set(result.trackId, result);
685
+ observedCertificate = new ObservedCertificate(stats.timestamp, stats.id, this);
332
686
 
333
- this.emit('newinboudvideotrack', result);
687
+ observedCertificate.update(stats);
334
688
 
335
- return result;
689
+ this.observedCertificates.set(stats.id, observedCertificate);
690
+ this.emit('added-certificate', observedCertificate);
691
+ } else {
692
+ observedCertificate.update(stats);
693
+ this.emit('updated-certificate', observedCertificate);
694
+ }
695
+
696
+ return observedCertificate;
336
697
  }
337
698
 
338
- public createOutboundAudioTrack(config: ObservedOutboundAudioTrackModel): ObservedOutboundAudioTrack {
339
- if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
340
-
341
- const result = new ObservedOutboundAudioTrack(config, this);
699
+ private _updateCodecStats(stats: CodecStats) {
700
+ let observedCodec = this.observedCodecs.get(stats.id);
342
701
 
343
- result.on('close', () => {
344
- this._outboundAudioTracks.delete(result.trackId);
345
- });
346
- this._outboundAudioTracks.set(result.trackId, result);
702
+ if (!observedCodec) {
703
+ if (!stats.timestamp || !stats.id || !stats.mimeType) {
704
+ return logger.warn(
705
+ `ObservedPeerConnection received an invalid CodecStats (missing timestamp OR id OR mimeType field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
706
+ stats
707
+ );
708
+ }
709
+
710
+ observedCodec = new ObservedCodec(stats.timestamp, stats.id, stats.mimeType, this);
347
711
 
348
- this.emit('newoutboundaudiotrack', result);
712
+ observedCodec.update(stats);
713
+
714
+ this.observedCodecs.set(stats.id, observedCodec);
715
+ this.emit('added-codec', observedCodec);
716
+ } else {
717
+ observedCodec.update(stats);
718
+ }
719
+ this.emit('updated-codec', observedCodec);
349
720
 
350
- return result;
721
+ return observedCodec;
351
722
  }
352
723
 
353
- public createOutboundVideoTrack(config: ObservedOutboundVideoTrackModel): ObservedOutboundVideoTrack {
354
- if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
355
-
356
- const result = new ObservedOutboundVideoTrack(config, this);
724
+ private _updateDataChannelStats(stats: DataChannelStats) {
725
+ let observedDataChannel = this.observedDataChannels.get(stats.id);
726
+
727
+ if (!observedDataChannel) {
728
+ if (!stats.timestamp || !stats.id) {
729
+ return logger.warn(
730
+ `ObservedPeerConnection received an invalid DataChannelStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
731
+ stats
732
+ );
733
+ }
357
734
 
358
- result.on('close', () => {
359
- this._outboundVideoTracks.delete(result.trackId);
360
- });
361
- this._outboundVideoTracks.set(result.trackId, result);
735
+ observedDataChannel = new ObservedDataChannel(stats.timestamp, stats.id, this);
362
736
 
363
- this.emit('newoutboundvideotrack', result);
737
+ observedDataChannel.update(stats);
364
738
 
365
- return result;
739
+ this.observedDataChannels.set(stats.id, observedDataChannel);
740
+ this.emit('added-data-channel', observedDataChannel);
741
+ } else {
742
+ observedDataChannel.update(stats);
743
+ }
744
+ this.emit('updated-data-channel', observedDataChannel);
745
+
746
+ return observedDataChannel;
366
747
  }
367
748
 
368
- public createDataChannel(channelId: number) {
369
- if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
370
-
371
- const result = new ObservedDataChannel({
372
- channelId,
373
- }, this);
749
+ private _updateIceCandidateStats(stats: IceCandidateStats) {
750
+ let observedIceCandidate = this.observedIceCandidates.get(stats.id);
374
751
 
375
- result.on('close', () => {
376
- this._dataChannels.delete(result.channelId);
377
- });
378
- this._dataChannels.set(result.channelId, result);
752
+ if (!observedIceCandidate) {
753
+ if (!stats.timestamp || !stats.id) {
754
+ return logger.warn(
755
+ `ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
756
+ stats
757
+ );
758
+ }
759
+
760
+ observedIceCandidate = new ObservedIceCandidate(stats.timestamp, stats.id, this);
379
761
 
380
- this.emit('newdatachannel', result);
762
+ observedIceCandidate.update(stats);
763
+
764
+ this.observedIceCandidates.set(stats.id, observedIceCandidate);
765
+ this.emit('added-ice-candidate', observedIceCandidate);
766
+ } else {
767
+ observedIceCandidate.update(stats);
768
+ }
769
+ this.emit('updated-ice-candidate', observedIceCandidate);
381
770
 
382
- return result;
771
+ return observedIceCandidate;
383
772
  }
384
773
 
385
- public updateMetrics() {
386
- let sumRttInMs = 0;
387
- let sumJitter = 0;
388
- const trackScores: CalculatedScore[] = [];
774
+ private _updateIceCandidatePairStats(stats: IceCandidateStats) {
775
+ let observedIceCandidatePair = this.observedIceCandidatesPair.get(stats.id);
776
+
777
+ if (!observedIceCandidatePair) {
778
+ if (!stats.timestamp || !stats.id) {
779
+ return logger.warn(
780
+ `ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
781
+ stats
782
+ );
783
+ }
389
784
 
390
- this._inboundAudioTracks.forEach((track) => {
391
- track.updateMetrics();
785
+ observedIceCandidatePair = new ObservedIceCandidatePair(stats.timestamp, stats.id, this);
392
786
 
393
- this.deltaInboundPacketsLost += track.deltaLostPackets;
394
- this.deltaInboundPacketsReceived += track.deltaReceivedPackets;
395
- this.deltaInboundReceivedBytes += track.deltaBytesReceived;
396
-
397
- this.deltaReceivedAudioBytes += track.deltaBytesReceived;
398
- this.deltaReceivedAudioPackets += track.deltaReceivedPackets;
787
+ observedIceCandidatePair.update(stats);
399
788
 
400
- this.receivingAudioBitrate += track.bitrate;
789
+ this.observedIceCandidatesPair.set(stats.id, observedIceCandidatePair);
790
+ this.emit('added-ice-candidate-pair', observedIceCandidatePair);
791
+ } else {
792
+ observedIceCandidatePair.update(stats);
793
+ }
794
+ this.emit('updated-ice-candidate-pair', observedIceCandidatePair);
795
+
796
+ return observedIceCandidatePair;
797
+ }
401
798
 
402
- sumRttInMs += (track.rttInMs ?? 0);
403
- sumJitter += (track.jitter ?? 0);
799
+ private _updateIceTransportStats(stats: IceCandidateStats) {
800
+ let observedIceTransport = this.observedIceTransports.get(stats.id);
404
801
 
405
- track.score && trackScores.push(track.score);
406
- });
802
+ if (!observedIceTransport) {
803
+ if (!stats.timestamp || !stats.id) {
804
+ return logger.warn(
805
+ `ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
806
+ stats
807
+ );
808
+ }
407
809
 
408
- this._inboundVideoTracks.forEach((track) => {
409
- track.updateMetrics();
810
+ observedIceTransport = new ObservedIceTransport(stats.timestamp, stats.id, this);
410
811
 
411
- this.deltaInboundPacketsLost += track.deltaLostPackets;
412
- this.deltaInboundPacketsReceived += track.deltaReceivedPackets;
413
- this.deltaInboundReceivedBytes += track.deltaBytesReceived;
414
-
415
- this.deltaReceivedVideoBytes += track.deltaBytesReceived;
416
- this.deltaReceivedVideoPackets += track.deltaReceivedPackets;
812
+ observedIceTransport.update(stats);
417
813
 
418
- this.receivingVideoBitrate += track.bitrate;
814
+ this.observedIceTransports.set(stats.id, observedIceTransport);
815
+ this.emit('added-ice-transport', observedIceTransport);
816
+ } else {
817
+ observedIceTransport.update(stats);
818
+ }
819
+ this.emit('updated-ice-transport', observedIceTransport);
419
820
 
420
- sumRttInMs += (track.rttInMs ?? 0);
421
- sumJitter += (track.jitter ?? 0);
821
+ return observedIceTransport;
822
+ }
422
823
 
423
- track.score && trackScores.push(track.score);
424
- });
824
+ private _updateInboundRtpStats(stats: InboundRtpStats) {
825
+ let observedInboundRtp = this.observedInboundRtps.get(stats.ssrc);
425
826
 
426
- this._outboundAudioTracks.forEach((track) => {
427
- track.updateMetrics();
827
+ if (!observedInboundRtp) {
828
+ if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind || !stats.trackIdentifier) {
829
+ return logger.warn(
830
+ `ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR ssrc OR kind OR trackIdentifier field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
831
+ stats
832
+ );
833
+ }
428
834
 
429
- this.deltaOutboundPacketsSent += track.deltaSentPackets;
430
- this.deltaOutboundSentBytes += track.deltaSentBytes;
835
+ observedInboundRtp = new ObservedInboundRtp(
836
+ stats.timestamp,
837
+ stats.id,
838
+ stats.ssrc,
839
+ stats.kind as MediaKind,
840
+ stats.trackIdentifier,
841
+ this
842
+ );
431
843
 
432
- this.deltaSentAudioBytes += track.deltaSentBytes;
433
- this.deltaSentAudioBytes += track.deltaSentPackets;
434
-
435
- this.sendingAudioBitrate += track.bitrate;
844
+ observedInboundRtp.update(stats);
436
845
 
437
- sumRttInMs += (track.rttInMs ?? 0);
438
- sumJitter += (track.jitter ?? 0);
846
+ this.observedInboundRtps.set(stats.ssrc, observedInboundRtp);
847
+ this.emit('added-inbound-rtp', observedInboundRtp);
848
+ } else {
849
+ observedInboundRtp.update(stats);
850
+ }
851
+ this.emit('updated-inbound-rtp', observedInboundRtp);
439
852
 
440
- track.score && trackScores.push(track.score);
441
- });
853
+ return observedInboundRtp;
854
+ }
442
855
 
443
- this._outboundVideoTracks.forEach((track) => {
444
- track.updateMetrics();
856
+ private _updateInboundTrackSample(stats: InboundTrackSample) {
857
+ let observedInboundTrack = this.observedInboundTracks.get(stats.id);
445
858
 
446
- this.deltaOutboundPacketsSent += track.deltaSentPackets;
447
- this.deltaOutboundSentBytes += track.deltaSentBytes;
448
-
449
- this.deltaSentVideoBytes += track.deltaSentBytes;
450
- this.deltaSentVideoBytes += track.deltaSentPackets;
859
+ if (!observedInboundTrack) {
860
+ if (!stats.timestamp || !stats.id || !stats.kind) {
861
+ return logger.warn(
862
+ `ObservedPeerConnection received an invalid InboundTrackSample (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
863
+ stats
864
+ );
865
+ }
866
+
867
+ const inboundRtp = [ ...this.observedInboundRtps.values() ].find((inbRtp) => inbRtp.trackIdentifier === stats.id);
868
+ const mediaPlayout = inboundRtp ?
869
+ [ ...this.observedMediaPlayouts.values() ].find((mp) => mp.id === inboundRtp.playoutId) : undefined;
870
+
871
+ observedInboundTrack = new ObservedInboundTrack(
872
+ stats.timestamp,
873
+ stats.id,
874
+ stats.kind as MediaKind,
875
+ this,
876
+ inboundRtp,
877
+ mediaPlayout,
878
+ );
879
+
880
+ observedInboundTrack.update(stats);
451
881
 
452
- this.sendingVideoBitrate += track.bitrate;
882
+ this.observedInboundTracks.set(stats.id, observedInboundTrack);
883
+ this.emit('added-inbound-track', observedInboundTrack);
884
+ } else {
885
+ observedInboundTrack.update(stats);
886
+ }
887
+ this.emit('updated-inbound-track', observedInboundTrack);
453
888
 
454
- sumRttInMs += (track.rttInMs ?? 0);
455
- sumJitter += (track.jitter ?? 0);
889
+ return observedInboundTrack;
890
+ }
456
891
 
457
- track.score && trackScores.push(track.score);
458
- });
892
+ private _updateMediaPlayoutStats(stats: MediaPlayoutStats) {
893
+ let observedMediaPlayout = this.observedMediaPlayouts.get(stats.id);
459
894
 
460
- this._dataChannels.forEach((channel) => {
461
- this.deltaDataChannelBytesSent += channel.deltaBytesSent;
462
- this.deltaDataChannelBytesReceived += channel.deltaBytesReceived;
463
- });
895
+ if (!observedMediaPlayout) {
896
+ if (!stats.timestamp || !stats.id || !stats.kind) {
897
+ return logger.warn(
898
+ `ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR kind OR trackIdentifier field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
899
+ stats
900
+ );
901
+ }
464
902
 
465
- const iceRttInMs = this.ICE.stats?.currentRoundTripTime;
466
- let nrOfBelongings = this._inboundAudioTracks.size + this._inboundVideoTracks.size + this._outboundAudioTracks.size + this._outboundVideoTracks.size;
903
+ observedMediaPlayout = new ObservedMediaPlayout(
904
+ stats.timestamp,
905
+ stats.id,
906
+ stats.kind as MediaKind,
907
+ this
908
+ );
467
909
 
468
- this.avgJitter = 0 < nrOfBelongings ? sumJitter / nrOfBelongings : undefined;
910
+ observedMediaPlayout.update(stats);
469
911
 
470
- if (iceRttInMs) {
471
- sumRttInMs += iceRttInMs;
472
- nrOfBelongings += 1;
912
+ this.observedMediaPlayouts.set(stats.id, observedMediaPlayout);
913
+ this.emit('added-media-playout', observedMediaPlayout);
914
+ } else {
915
+ observedMediaPlayout.update(stats);
473
916
  }
917
+ this.emit('updated-media-playout', observedMediaPlayout);
474
918
 
475
- this.avgRttInMs = 0 < nrOfBelongings ? sumRttInMs / nrOfBelongings : undefined;
476
- this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
477
- this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
478
- this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
479
- this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
480
- this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
481
- this.totalSentAudioBytes += this.deltaSentAudioBytes;
482
- this.totalSentVideoBytes += this.deltaSentVideoBytes;
483
- this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
484
- this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
485
- this.totalReceivedAudioPacktes += this.deltaReceivedAudioPackets;
486
- this.totalReceivedVideoPackets += this.deltaReceivedVideoPackets;
487
- this.totalSentAudioPackets += this.deltaSentAudioBytes;
488
- this.totalSentVideoPackets += this.deltaSentVideoBytes;
919
+ return observedMediaPlayout;
920
+ }
921
+
922
+ private _updateMediaSourceStats(stats: MediaSourceStats) {
923
+ let observedMediaSource = this.observedMediaSources.get(stats.id);
924
+
925
+ if (!observedMediaSource) {
926
+ if (!stats.timestamp || !stats.id || !stats.kind) {
927
+ return logger.warn(
928
+ `ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
929
+ stats
930
+ );
931
+ }
932
+
933
+ observedMediaSource = new ObservedMediaSource(
934
+ stats.timestamp,
935
+ stats.id,
936
+ stats.kind as MediaKind,
937
+ this
938
+ );
939
+
940
+ observedMediaSource.update(stats);
941
+
942
+ this.observedMediaSources.set(stats.id, observedMediaSource);
943
+ this.emit('added-media-source', observedMediaSource);
944
+ } else {
945
+ observedMediaSource.update(stats);
946
+ }
947
+ this.emit('updated-media-source', observedMediaSource);
948
+
949
+ return observedMediaSource;
950
+ }
951
+
952
+ private _updateOutboundRtpStats(stats: OutboundRtpStats) {
953
+ let observedOutboundRtp = this.observedOutboundRtps.get(stats.ssrc);
954
+
955
+ if (!observedOutboundRtp) {
956
+ if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
957
+ return logger.warn(
958
+ `ObservedPeerConnection received an invalid OutboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
959
+ stats
960
+ );
961
+ }
962
+
963
+ observedOutboundRtp = new ObservedOutboundRtp(
964
+ stats.timestamp,
965
+ stats.id,
966
+ stats.ssrc,
967
+ stats.kind as MediaKind,
968
+ this
969
+ );
970
+
971
+ observedOutboundRtp.update(stats);
972
+
973
+ this.observedOutboundRtps.set(stats.ssrc, observedOutboundRtp);
974
+ this.emit('added-outbound-rtp', observedOutboundRtp);
975
+ } else {
976
+ observedOutboundRtp.update(stats);
977
+ }
978
+ this.emit('updated-outbound-rtp', observedOutboundRtp);
979
+
980
+ return observedOutboundRtp;
981
+ }
982
+
983
+ public _updateOutboundTrackSample(stats: OutboundTrackSample) {
984
+ let observedOutboundTrack = this.observedOutboundTracks.get(stats.id);
489
985
 
490
- if (this._elapsedTimeSinceLastUpdate && 0 < this._elapsedTimeSinceLastUpdate) {
491
- this.sendingPacketsPerSecond = this.deltaOutboundPacketsSent / (this._elapsedTimeSinceLastUpdate / 1000);
492
- this.receivingPacketsPerSecond = this.deltaInboundPacketsReceived / (this._elapsedTimeSinceLastUpdate / 1000);
986
+ if (!observedOutboundTrack) {
987
+ if (!stats.timestamp || !stats.id || !stats.kind) {
988
+ return logger.warn(
989
+ `ObservedPeerConnection received an invalid OutboundTrackSample (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
990
+ stats
991
+ );
992
+ }
993
+ const observedMediaSource = [ ...this.observedMediaSources.values() ].find((mediaSource) => mediaSource.trackIdentifier === stats.id);
994
+ const outboundRtps = observedMediaSource
995
+ ? [ ...this.observedOutboundRtps.values() ].filter((outboundRtp) => outboundRtp.mediaSourceId === observedMediaSource?.id) : undefined;
996
+
997
+ observedOutboundTrack = new ObservedOutboundTrack(
998
+ stats.timestamp,
999
+ stats.id,
1000
+ stats.kind as MediaKind,
1001
+ this,
1002
+ outboundRtps,
1003
+ observedMediaSource,
1004
+ );
1005
+
1006
+ observedOutboundTrack.update(stats);
1007
+
1008
+ this.observedOutboundTracks.set(stats.id, observedOutboundTrack);
1009
+ this.emit('added-outbound-track', observedOutboundTrack);
493
1010
  } else {
494
- this.sendingPacketsPerSecond = 0;
495
- this.receivingPacketsPerSecond = 0;
496
- }
497
-
498
- // calculate quality score
499
- this._updateQualityScore(trackScores);
500
- }
501
-
502
- private _updateQualityScore(trackScores: CalculatedScore[]) {
503
- // Packet Jitter measured in seconds
504
- // we use RTT and lost packets to calculate the base score for the connection
505
- const rttInMs = this.avgRttInMs ?? 0;
506
- const latencyFactor = rttInMs < 150 ? 1.0 : getRttScore(rttInMs);
507
- const totalPackets = Math.max(1, (this.totalInboundPacketsReceived ?? 0) + (this.totalOutboundPacketsSent ?? 0));
508
- const lostPackets = (this.totalInboundPacketsLost ?? 0) + (this.deltaOutboundPacketsSent ?? 0);
509
- const deliveryFactor = 1.0 - ((lostPackets) / (lostPackets + totalPackets));
510
- // let's push the actual stability score
511
- const stabilityScore = ((latencyFactor * 0.5) + (deliveryFactor * 0.5)) ** 2;
512
-
513
- this._stabilityScores.push(stabilityScore);
514
- if (10 < this._stabilityScores.length) {
515
- this._stabilityScores.shift();
516
- } else if (this._stabilityScores.length < 5) {
517
- return;
518
- }
519
- let counter = 0;
520
- let weight = 0;
521
- let totalScore = 0;
522
-
523
- for (const score of this._stabilityScores) {
524
- weight += 1;
525
- counter += weight;
526
- totalScore += weight * score;
527
- }
528
- const weightedStabilityScore = totalScore / counter;
529
- let sumTrackScores = 0;
530
-
531
- for (const trackScore of trackScores) {
532
- sumTrackScores += trackScore.score;
533
- }
534
-
535
- const avgTrackScores = sumTrackScores / trackScores.length;
536
-
537
- const score: CalculatedScore = {
538
- remarks: [ {
539
- severity: 'none',
540
- text: `Peer Connection stability score: ${weightedStabilityScore}`,
541
- },
542
- {
543
- severity: 'none',
544
- text: `Avg. Track score: ${avgTrackScores}`,
545
- } ],
546
- score: Math.floor((weightedStabilityScore + avgTrackScores) * 5),
547
- timestamp: this._statsTimestamp ?? Date.now(),
548
- };
549
-
550
- for (const pendingIssue of this.ωpendingIssuesForScores) {
551
- switch (pendingIssue.severity) {
552
- case 'critical':
553
- score.score = 0.0;
554
- break;
555
- case 'major':
556
- score.score *= 0.5;
557
- break;
558
- case 'minor':
559
- score.score *= 0.8;
560
- break;
561
- }
562
- score.remarks.push({
563
- severity: pendingIssue.severity,
564
- text: pendingIssue.description ?? 'Issue occurred',
565
- });
566
- }
567
-
568
- this.ωpendingIssuesForScores = [];
569
- this.score = score;
570
-
571
- this.emit('score', score);
572
- }
573
- }
1011
+ observedOutboundTrack.update(stats);
1012
+ }
1013
+ this.emit('updated-outbound-track', observedOutboundTrack);
1014
+
1015
+ return observedOutboundTrack;
1016
+ }
1017
+
1018
+ private _updatePeerConnectionTransportStats(stats: PeerConnectionTransportStats) {
1019
+ let observedPeerConnectionTransport = this.observedPeerConnectionTransports.get(stats.id);
1020
+
1021
+ if (!observedPeerConnectionTransport) {
1022
+ if (!stats.timestamp || !stats.id) {
1023
+ return logger.warn(
1024
+ `ObservedPeerConnection received an invalid PeerConnectionTransportStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
1025
+ stats
1026
+ );
1027
+ }
1028
+
1029
+ observedPeerConnectionTransport = new ObservedPeerConnectionTransport(stats.timestamp, stats.id, this);
1030
+
1031
+ observedPeerConnectionTransport.update(stats);
1032
+
1033
+ this.observedPeerConnectionTransports.set(stats.id, observedPeerConnectionTransport);
1034
+ this.emit('added-peer-connection-transport', observedPeerConnectionTransport);
1035
+ } else {
1036
+ observedPeerConnectionTransport.update(stats);
1037
+ }
1038
+ this.emit('updated-peer-connection-transport', observedPeerConnectionTransport);
1039
+
1040
+ return observedPeerConnectionTransport;
1041
+ }
1042
+
1043
+ private _updateRemoteInboundRtpStats(stats: RemoteInboundRtpStats) {
1044
+ let observedRemoteInboundRtp = this.observedRemoteInboundRtps.get(stats.ssrc);
1045
+
1046
+ if (!observedRemoteInboundRtp) {
1047
+ if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
1048
+ return logger.warn(
1049
+ `ObservedPeerConnection received an invalid RemoteInboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
1050
+ stats
1051
+ );
1052
+ }
1053
+
1054
+ observedRemoteInboundRtp = new ObservedRemoteInboundRtp(
1055
+ stats.timestamp,
1056
+ stats.id,
1057
+ stats.ssrc,
1058
+ stats.kind as MediaKind,
1059
+ this
1060
+ );
1061
+
1062
+ observedRemoteInboundRtp.update(stats);
1063
+
1064
+ this.observedRemoteInboundRtps.set(stats.ssrc, observedRemoteInboundRtp);
1065
+ this.emit('added-remote-inbound-rtp', observedRemoteInboundRtp);
1066
+ } else {
1067
+ observedRemoteInboundRtp.update(stats);
1068
+ }
1069
+ this.emit('updated-remote-inbound-rtp', observedRemoteInboundRtp);
1070
+
1071
+ return observedRemoteInboundRtp;
1072
+ }
1073
+
1074
+ private _updateRemoteOutboundRtpStats(stats: RemoteOutboundRtpStats) {
1075
+ let observedRemoteOutboundRtp = this.observedRemoteOutboundRtps.get(stats.ssrc);
1076
+
1077
+ if (!observedRemoteOutboundRtp) {
1078
+ if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
1079
+ return logger.warn(
1080
+ `ObservedPeerConnection received an invalid RemoteOutboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
1081
+ stats
1082
+ );
1083
+ }
1084
+
1085
+ observedRemoteOutboundRtp = new ObservedRemoteOutboundRtp(
1086
+ stats.timestamp,
1087
+ stats.id,
1088
+ stats.ssrc,
1089
+ stats.kind as MediaKind,
1090
+ this
1091
+ );
1092
+
1093
+ observedRemoteOutboundRtp.update(stats);
1094
+
1095
+ this.observedRemoteOutboundRtps.set(stats.ssrc, observedRemoteOutboundRtp);
1096
+ this.emit('added-remote-outbound-rtp', observedRemoteOutboundRtp);
1097
+ } else {
1098
+ observedRemoteOutboundRtp.update(stats);
1099
+ }
1100
+ this.emit('updated-remote-outbound-rtp', observedRemoteOutboundRtp);
1101
+
1102
+ return observedRemoteOutboundRtp;
1103
+ }
1104
+ }