@observertc/observer-js 0.42.8 → 1.0.0-beta.1

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