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