@observertc/observer-js 0.42.9 → 1.0.1-23e88d8.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 (239) hide show
  1. package/.prettierrc +6 -6
  2. package/README.md +0 -104
  3. package/lib/ObservedCall.d.ts +25 -41
  4. package/lib/ObservedCall.d.ts.map +1 -1
  5. package/lib/ObservedCall.js +61 -109
  6. package/lib/ObservedCertificate.d.ts +19 -0
  7. package/lib/ObservedCertificate.d.ts.map +1 -0
  8. package/lib/ObservedCertificate.js +38 -0
  9. package/lib/ObservedClient.d.ts +33 -143
  10. package/lib/ObservedClient.d.ts.map +1 -1
  11. package/lib/ObservedClient.js +156 -757
  12. package/lib/ObservedCodec.d.ts +22 -0
  13. package/lib/ObservedCodec.d.ts.map +1 -0
  14. package/lib/ObservedCodec.js +45 -0
  15. package/lib/ObservedDataChannel.d.ts +20 -70
  16. package/lib/ObservedDataChannel.d.ts.map +1 -1
  17. package/lib/ObservedDataChannel.js +39 -109
  18. package/lib/ObservedIceCandidate.d.ts +29 -0
  19. package/lib/ObservedIceCandidate.d.ts.map +1 -0
  20. package/lib/ObservedIceCandidate.js +59 -0
  21. package/lib/ObservedIceCandidatePair.d.ts +40 -0
  22. package/lib/ObservedIceCandidatePair.d.ts.map +1 -0
  23. package/lib/ObservedIceCandidatePair.js +83 -0
  24. package/lib/ObservedIceTransport.d.ts +32 -0
  25. package/lib/ObservedIceTransport.d.ts.map +1 -0
  26. package/lib/ObservedIceTransport.js +65 -0
  27. package/lib/ObservedInboundRtp.d.ts +92 -0
  28. package/lib/ObservedInboundRtp.d.ts.map +1 -0
  29. package/lib/ObservedInboundRtp.js +193 -0
  30. package/lib/ObservedInboundTrack.d.ts +29 -0
  31. package/lib/ObservedInboundTrack.d.ts.map +1 -0
  32. package/lib/ObservedInboundTrack.js +57 -0
  33. package/lib/ObservedMediaPlayout.d.ts +22 -0
  34. package/lib/ObservedMediaPlayout.d.ts.map +1 -0
  35. package/lib/ObservedMediaPlayout.js +42 -0
  36. package/lib/ObservedMediaSource.d.ts +28 -0
  37. package/lib/ObservedMediaSource.d.ts.map +1 -0
  38. package/lib/ObservedMediaSource.js +55 -0
  39. package/lib/ObservedOutboundRtp.d.ts +62 -0
  40. package/lib/ObservedOutboundRtp.d.ts.map +1 -0
  41. package/lib/ObservedOutboundRtp.js +144 -0
  42. package/lib/ObservedOutboundTrack.d.ts +29 -0
  43. package/lib/ObservedOutboundTrack.d.ts.map +1 -0
  44. package/lib/ObservedOutboundTrack.js +57 -0
  45. package/lib/ObservedPeerConnection.d.ts +118 -78
  46. package/lib/ObservedPeerConnection.d.ts.map +1 -1
  47. package/lib/ObservedPeerConnection.js +556 -359
  48. package/lib/ObservedPeerConnectionTransport.d.ts +17 -0
  49. package/lib/ObservedPeerConnectionTransport.d.ts.map +1 -0
  50. package/lib/ObservedPeerConnectionTransport.js +34 -0
  51. package/lib/ObservedRemoteInboundRtp.d.ts +30 -0
  52. package/lib/ObservedRemoteInboundRtp.d.ts.map +1 -0
  53. package/lib/ObservedRemoteInboundRtp.js +60 -0
  54. package/lib/ObservedRemoteOutboundRtp.d.ts +30 -0
  55. package/lib/ObservedRemoteOutboundRtp.d.ts.map +1 -0
  56. package/lib/ObservedRemoteOutboundRtp.js +60 -0
  57. package/lib/Observer.d.ts +30 -53
  58. package/lib/Observer.d.ts.map +1 -1
  59. package/lib/Observer.js +52 -225
  60. package/lib/common/Middleware.d.ts +10 -1
  61. package/lib/common/Middleware.d.ts.map +1 -1
  62. package/lib/common/Middleware.js +55 -36
  63. package/lib/common/logger.d.ts +8 -4
  64. package/lib/common/logger.d.ts.map +1 -1
  65. package/lib/common/logger.js +44 -68
  66. package/lib/common/types.d.ts +0 -21
  67. package/lib/common/types.d.ts.map +1 -1
  68. package/lib/common/types.js +1 -0
  69. package/lib/common/utils.d.ts +1 -0
  70. package/lib/common/utils.d.ts.map +1 -1
  71. package/lib/common/utils.js +13 -1
  72. package/lib/detectors/Detector.d.ts +5 -0
  73. package/lib/detectors/Detector.d.ts.map +1 -0
  74. package/lib/detectors/Detector.js +3 -0
  75. package/lib/detectors/Detectors.d.ts +11 -0
  76. package/lib/detectors/Detectors.d.ts.map +1 -0
  77. package/lib/detectors/Detectors.js +34 -0
  78. package/lib/index.d.ts +23 -25
  79. package/lib/index.d.ts.map +1 -1
  80. package/lib/index.js +41 -41
  81. package/lib/monitors/TurnUsageMonitor.d.ts +0 -52
  82. package/lib/monitors/TurnUsageMonitor.d.ts.map +1 -1
  83. package/lib/monitors/TurnUsageMonitor.js +145 -120
  84. package/lib/schema/ClientEventTypes.d.ts +33 -0
  85. package/lib/schema/ClientEventTypes.d.ts.map +1 -0
  86. package/lib/schema/ClientEventTypes.js +38 -0
  87. package/lib/schema/ClientMetaTypes.d.ts +34 -0
  88. package/lib/schema/ClientMetaTypes.d.ts.map +1 -0
  89. package/lib/schema/ClientMetaTypes.js +16 -0
  90. package/lib/schema/ClientSample.d.ts +1333 -0
  91. package/lib/schema/ClientSample.d.ts.map +1 -0
  92. package/lib/schema/ClientSample.js +4 -0
  93. package/lib/scores/CalculatedScore.d.ts +6 -0
  94. package/lib/scores/CalculatedScore.d.ts.map +1 -0
  95. package/lib/scores/DefaultCallScoreCalculator.d.ts +7 -0
  96. package/lib/scores/DefaultCallScoreCalculator.d.ts.map +1 -0
  97. package/lib/scores/DefaultCallScoreCalculator.js +21 -0
  98. package/lib/scores/ScoreCalculator.d.ts +4 -0
  99. package/lib/scores/ScoreCalculator.d.ts.map +1 -0
  100. package/lib/scores/ScoreCalculator.js +2 -0
  101. package/lib/utils/CallUpdater.d.ts +5 -0
  102. package/lib/utils/CallUpdater.d.ts.map +1 -0
  103. package/lib/utils/CallUpdater.js +2 -0
  104. package/lib/utils/OnAllClientCallUpdater.d.ts +16 -0
  105. package/lib/utils/OnAllClientCallUpdater.d.ts.map +1 -0
  106. package/lib/utils/OnAllClientCallUpdater.js +58 -0
  107. package/lib/utils/RemoteTrackResolver.d.ts +7 -0
  108. package/lib/utils/RemoteTrackResolver.d.ts.map +1 -0
  109. package/lib/utils/RemoteTrackResolver.js +2 -0
  110. package/lib/utils/onAnyClientCallUpdater.d.ts +14 -0
  111. package/lib/utils/onAnyClientCallUpdater.d.ts.map +1 -0
  112. package/lib/utils/onAnyClientCallUpdater.js +51 -0
  113. package/package.json +4 -4
  114. package/src/ObservedCall.ts +74 -167
  115. package/src/ObservedCertificate.ts +43 -0
  116. package/src/ObservedClient.ts +173 -1085
  117. package/src/ObservedCodec.ts +49 -0
  118. package/src/ObservedDataChannel.ts +36 -178
  119. package/src/ObservedIceCandidate.ts +64 -0
  120. package/src/ObservedIceCandidatePair.ts +92 -0
  121. package/src/ObservedIceTransport.ts +70 -0
  122. package/src/ObservedInboundRtp.ts +205 -0
  123. package/src/ObservedInboundTrack.ts +70 -0
  124. package/src/ObservedMediaPlayout.ts +49 -0
  125. package/src/ObservedMediaSource.ts +60 -0
  126. package/src/ObservedOutboundRtp.ts +156 -0
  127. package/src/ObservedOutboundTrack.ts +69 -0
  128. package/src/ObservedPeerConnection.ts +743 -401
  129. package/src/ObservedPeerConnectionTransport.ts +38 -0
  130. package/src/ObservedRemoteInboundRtp.ts +64 -0
  131. package/src/ObservedRemoteOutboundRtp.ts +65 -0
  132. package/src/Observer.ts +65 -285
  133. package/src/common/Middleware.ts +70 -42
  134. package/src/common/logger.ts +60 -69
  135. package/src/common/types.ts +0 -42
  136. package/src/common/utils.ts +12 -0
  137. package/src/detectors/Detector.ts +6 -0
  138. package/src/detectors/Detectors.ts +38 -0
  139. package/src/index.ts +23 -90
  140. package/src/monitors/TurnUsageMonitor.ts +164 -164
  141. package/src/schema/ClientEventTypes.ts +36 -0
  142. package/src/schema/ClientMetaTypes.ts +41 -0
  143. package/src/schema/ClientSample.ts +1682 -0
  144. package/src/scores/CalculatedScore.ts +6 -0
  145. package/src/scores/DefaultCallScoreCalculator.ts +23 -0
  146. package/src/scores/ScoreCalculator.ts +3 -0
  147. package/src/utils/CallUpdater.ts +5 -0
  148. package/src/utils/OnAllClientCallUpdater.ts +68 -0
  149. package/src/utils/RemoteTrackResolver.ts +12 -0
  150. package/src/utils/onAnyClientCallUpdater.ts +59 -0
  151. package/tsconfig.json +3 -3
  152. package/lib/ObservedICE.d.ts +0 -64
  153. package/lib/ObservedICE.d.ts.map +0 -1
  154. package/lib/ObservedICE.js +0 -141
  155. package/lib/ObservedInboundAudioTrack.d.ts +0 -96
  156. package/lib/ObservedInboundAudioTrack.d.ts.map +0 -1
  157. package/lib/ObservedInboundAudioTrack.js +0 -217
  158. package/lib/ObservedInboundVideoTrack.d.ts +0 -101
  159. package/lib/ObservedInboundVideoTrack.d.ts.map +0 -1
  160. package/lib/ObservedInboundVideoTrack.js +0 -232
  161. package/lib/ObservedOutboundAudioTrack.d.ts +0 -87
  162. package/lib/ObservedOutboundAudioTrack.d.ts.map +0 -1
  163. package/lib/ObservedOutboundAudioTrack.js +0 -186
  164. package/lib/ObservedOutboundVideoTrack.d.ts +0 -91
  165. package/lib/ObservedOutboundVideoTrack.d.ts.map +0 -1
  166. package/lib/ObservedOutboundVideoTrack.js +0 -193
  167. package/lib/ObservedSfu.d.ts +0 -59
  168. package/lib/ObservedSfu.d.ts.map +0 -1
  169. package/lib/ObservedSfu.js +0 -157
  170. package/lib/ObservedSfuInboundRtpPad.d.ts +0 -42
  171. package/lib/ObservedSfuInboundRtpPad.d.ts.map +0 -1
  172. package/lib/ObservedSfuInboundRtpPad.js +0 -71
  173. package/lib/ObservedSfuOutboundRtpPad.d.ts +0 -42
  174. package/lib/ObservedSfuOutboundRtpPad.d.ts.map +0 -1
  175. package/lib/ObservedSfuOutboundRtpPad.js +0 -72
  176. package/lib/ObservedSfuSctpChannel.d.ts +0 -42
  177. package/lib/ObservedSfuSctpChannel.d.ts.map +0 -1
  178. package/lib/ObservedSfuSctpChannel.js +0 -57
  179. package/lib/ObservedSfuTransport.d.ts +0 -58
  180. package/lib/ObservedSfuTransport.d.ts.map +0 -1
  181. package/lib/ObservedSfuTransport.js +0 -120
  182. package/lib/RemoteTrackAssigner.d.ts +0 -14
  183. package/lib/RemoteTrackAssigner.d.ts.map +0 -1
  184. package/lib/RemoteTrackAssigner.js +0 -52
  185. package/lib/ReportsCollector.d.ts +0 -109
  186. package/lib/ReportsCollector.d.ts.map +0 -1
  187. package/lib/ReportsCollector.js +0 -207
  188. package/lib/common/CalculatedScore.d.ts +0 -80
  189. package/lib/common/CalculatedScore.d.ts.map +0 -1
  190. package/lib/common/CalculatedScore.js +0 -162
  191. package/lib/common/CallEventType.d.ts +0 -63
  192. package/lib/common/CallEventType.d.ts.map +0 -1
  193. package/lib/common/CallEventType.js +0 -39
  194. package/lib/common/CallMetaReports.d.ts +0 -42
  195. package/lib/common/CallMetaReports.d.ts.map +0 -1
  196. package/lib/common/CallMetaReports.js +0 -36
  197. package/lib/common/QualityScoreUtils.d.ts +0 -1
  198. package/lib/common/QualityScoreUtils.d.ts.map +0 -1
  199. package/lib/common/QualityScoreUtils.js +0 -1
  200. package/lib/common/TypedEventEmitter.d.ts +0 -22
  201. package/lib/common/TypedEventEmitter.d.ts.map +0 -1
  202. package/lib/common/TypedEventEmitter.js +0 -11
  203. package/lib/common/callEventReports.d.ts +0 -10
  204. package/lib/common/callEventReports.d.ts.map +0 -1
  205. package/lib/common/callEventReports.js +0 -112
  206. package/lib/monitors/CallSummary.d.ts +0 -37
  207. package/lib/monitors/CallSummary.d.ts.map +0 -1
  208. package/lib/monitors/CallSummaryMonitor.d.ts +0 -32
  209. package/lib/monitors/CallSummaryMonitor.d.ts.map +0 -1
  210. package/lib/monitors/CallSummaryMonitor.js +0 -152
  211. package/lib/monitors/ClientIssueMonitor.d.ts +0 -32
  212. package/lib/monitors/ClientIssueMonitor.d.ts.map +0 -1
  213. package/lib/monitors/ClientIssueMonitor.js +0 -49
  214. package/lib/monitors/SfuServerMonitor.d.ts +0 -79
  215. package/lib/monitors/SfuServerMonitor.d.ts.map +0 -1
  216. package/lib/monitors/SfuServerMonitor.js +0 -198
  217. package/src/ObservedICE.ts +0 -237
  218. package/src/ObservedInboundAudioTrack.ts +0 -344
  219. package/src/ObservedInboundVideoTrack.ts +0 -361
  220. package/src/ObservedOutboundAudioTrack.ts +0 -307
  221. package/src/ObservedOutboundVideoTrack.ts +0 -319
  222. package/src/ObservedSfu.ts +0 -231
  223. package/src/ObservedSfuInboundRtpPad.ts +0 -106
  224. package/src/ObservedSfuOutboundRtpPad.ts +0 -107
  225. package/src/ObservedSfuSctpChannel.ts +0 -103
  226. package/src/ObservedSfuTransport.ts +0 -185
  227. package/src/RemoteTrackAssigner.ts +0 -68
  228. package/src/ReportsCollector.ts +0 -336
  229. package/src/common/CalculatedScore.ts +0 -188
  230. package/src/common/CallEventType.ts +0 -69
  231. package/src/common/CallMetaReports.ts +0 -82
  232. package/src/common/QualityScoreUtils.ts +0 -0
  233. package/src/common/TypedEventEmitter.ts +0 -32
  234. package/src/common/callEventReports.ts +0 -178
  235. package/src/monitors/CallSummary.ts +0 -38
  236. package/src/monitors/CallSummaryMonitor.ts +0 -192
  237. package/src/monitors/ClientIssueMonitor.ts +0 -88
  238. package/src/monitors/SfuServerMonitor.ts +0 -285
  239. /package/lib/{monitors/CallSummary.js → scores/CalculatedScore.js} +0 -0
@@ -1,1171 +1,259 @@
1
- import { Browser, ClientSample, Engine, IceLocalCandidate, IceRemoteCandidate, MediaCodecStats, MediaDevice, OperationSystem, Platform } from '@observertc/sample-schemas-js';
2
- import { ObservedCall } from './ObservedCall';
3
1
  import { EventEmitter } from 'events';
4
2
  import { ObservedPeerConnection } from './ObservedPeerConnection';
5
3
  import { createLogger } from './common/logger';
6
- import { CallMetaType, createCallMetaReport } from './common/CallMetaReports';
7
4
  // eslint-disable-next-line camelcase
8
- import { PartialBy, isValidUuid } from './common/utils';
9
- import { CallEventType } from './common/CallEventType';
10
- import { ObservedSfu } from './ObservedSfu';
11
- import { ClientIssue } from './monitors/CallSummary';
12
- import { CallEventReport } from '@observertc/report-schemas-js';
13
- import { CalculatedScore } from './common/CalculatedScore';
5
+ import { ClientEvent, ClientMetaData, ClientSample, PeerConnectionSample, ClientIssue, ExtensionStat } from './schema/ClientSample';
6
+ import * as MetaData from './schema/ClientMetaTypes';
7
+ import { ClientEventTypes } from './schema/ClientEventTypes';
8
+ import { ObservedCall } from './ObservedCall';
9
+ import { ClientMetaTypes } from './schema/ClientMetaTypes';
10
+ import { parseJsonAs } from './common/utils';
11
+ import { CalculatedScore } from './scores/CalculatedScore';
12
+ import { Detectors } from './detectors/Detectors';
14
13
 
15
14
  const logger = createLogger('ObservedClient');
16
15
 
17
- export type ObservedClientModel= {
16
+ export type ObservedClientSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = {
18
17
  clientId: string;
19
- mediaUnitId: string;
20
- userId?: string,
21
- marker?: string,
22
- operationSystem?: OperationSystem,
23
- engine?: Engine,
24
- platform?: Platform,
25
- browser?: Browser,
26
- coordinates?: {
27
- latitude: number;
28
- longitude: number;
29
- },
18
+ appData?: AppData;
30
19
  };
31
20
 
32
21
  export type ObservedClientEvents = {
33
- update: [{
34
- sample: ClientSample,
35
- elapsedTimeInMs: number,
36
- }],
37
- close: [],
38
- joined: [],
39
- left: [],
40
- newpeerconnection: [ObservedPeerConnection],
41
- iceconnectionstatechange: [{
42
- peerConnection: ObservedPeerConnection,
43
- state: string,
44
- }],
45
- icegatheringstatechange: [{
46
- peerConnection: ObservedPeerConnection,
47
- state: string,
48
- }],
49
- connectionstatechange: [{
50
- peerConnection: ObservedPeerConnection,
51
- state: string,
52
- }],
53
- selectedcandidatepair: [{
54
- peerConnection: ObservedPeerConnection,
55
- localCandidate: IceLocalCandidate,
56
- remoteCandidate: IceRemoteCandidate,
57
- }],
58
- issue: [ClientIssue],
59
- usingturn: [boolean],
60
- usermediaerror: [string],
61
- rejoined: [{
62
- lastJoined: number,
63
- }],
64
- score: [CalculatedScore],
22
+ update: [
23
+ {
24
+ sample: ClientSample;
25
+ elapsedTimeInMs: number;
26
+ }
27
+ ];
28
+ close: [];
29
+ joined: [];
30
+ rejoined: [timestamp: number];
31
+ left: [];
32
+ usingturn: [boolean];
33
+ usermediaerror: [string];
34
+
35
+ // do we need this?
36
+ newpeerconnection: [ObservedPeerConnection];
65
37
  };
66
38
 
67
- export declare interface ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> {
39
+ export declare interface ObservedClient {
68
40
  on<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
69
41
  off<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
70
42
  once<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this;
71
43
  emit<U extends keyof ObservedClientEvents>(event: U, ...args: ObservedClientEvents[U]): boolean;
72
- readonly appData: AppData;
73
44
  }
74
45
 
75
- type PendingPeerConnectionTimestamp = {
76
- type: 'opened' | 'closed'
77
- peerConnectionId: string;
78
- timestamp: number;
79
- }
46
+ export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
47
+ public readonly detectors: Detectors;
80
48
 
81
- type PendingMediaTrackTimestamp = {
82
- type: 'added' | 'removed'
83
- peerConnectionId: string;
84
- mediaTrackId: string;
85
- timestamp: number;
86
- }
49
+ public readonly clientId: string;
50
+ public readonly observedPeerConnections = new Map<string, ObservedPeerConnection>();
51
+ public readonly calculatedScore: CalculatedScore = {
52
+ weight: 1,
53
+ value: undefined,
54
+ };
55
+
56
+ public appData?: AppData;
57
+ public attachments?: Record<string, unknown>;
87
58
 
88
- export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
89
-
90
- public readonly created = Date.now();
91
59
  public updated = Date.now();
92
- public sfuId?: string;
93
-
94
- private readonly _peerConnections = new Map<string, ObservedPeerConnection>();
95
-
96
- private _closed = false;
97
-
98
- private _acceptedSamples = 0;
99
- private _timeZoneOffsetInHours?: number;
60
+ public acceptedSamples = 0;
61
+ public closed = false;
100
62
 
63
+ public joinedAt?: number;
64
+ public leftAt?: number;
101
65
  // the timestamp of the CLIENT_JOINED event
102
- private _joined?: number;
103
- private _left?: number;
104
- public lastStatsTimestamp?: number;
66
+ public operationSystem?: MetaData.OperationSystem;
67
+ public engine?: MetaData.Engine;
68
+ public platform?: MetaData.Platform;
69
+ public browser?: MetaData.Browser;
70
+ public mediaConstraints: string[] = [];
105
71
 
106
- public score?: CalculatedScore;
107
72
  public usingTURN = false;
108
73
  public availableOutgoingBitrate = 0;
109
74
  public availableIncomingBitrate = 0;
110
- public totalInboundPacketsLost = 0;
111
- public totalInboundPacketsReceived = 0;
112
- public totalOutboundPacketsSent = 0;
113
- public totalDataChannelBytesSent = 0;
114
- public totalDataChannelBytesReceived = 0;
115
- public totalSentBytes = 0;
116
- public totalReceivedBytes = 0;
117
- public totalReceivedAudioBytes = 0;
118
- public totalReceivedVideoBytes = 0;
119
- public totalSentAudioBytes = 0;
120
- public totalSentVideoBytes = 0;
121
-
122
- public deltaReceivedAudioBytes = 0;
123
- public deltaReceivedVideoBytes = 0;
124
- public deltaSentAudioBytes = 0;
125
- public deltaSentVideoBytes = 0;
126
- public deltaDataChannelBytesSent = 0;
127
- public deltaDataChannelBytesReceived = 0;
128
- public deltaInboundPacketsLost = 0;
129
- public deltaInboundPacketsReceived = 0;
130
- public deltaOutboundPacketsSent = 0;
75
+ // public totalInboundPacketsLost = 0;
76
+ // public totalInboundPacketsReceived = 0;
77
+ // public totalOutboundPacketsSent = 0;
78
+ // public totalDataChannelBytesSent = 0;
79
+ // public totalDataChannelBytesReceived = 0;
80
+ // public totalSentBytes = 0;
81
+ // public totalReceivedBytes = 0;
82
+ // public totalReceivedAudioBytes = 0;
83
+ // public totalReceivedVideoBytes = 0;
84
+ // public totalSentAudioBytes = 0;
85
+ // public totalSentVideoBytes = 0;
86
+
87
+ // public deltaReceivedAudioBytes = 0;
88
+ // public deltaReceivedVideoBytes = 0;
89
+ // public deltaSentAudioBytes = 0;
90
+ // public deltaSentVideoBytes = 0;
91
+ // public deltaDataChannelBytesSent = 0;
92
+ // public deltaDataChannelBytesReceived = 0;
93
+ // public deltaInboundPacketsLost = 0;
94
+ // public deltaInboundPacketsReceived = 0;
95
+ // public deltaOutboundPacketsSent = 0;
131
96
 
132
97
  public avgRttInMs?: number;
133
98
  public outboundAudioBitrate?: number;
134
99
  public outboundVideoBitrate?: number;
135
100
  public inboundAudioBitrate?: number;
136
101
  public inboundVideoBitrate?: number;
137
- public mediaConstraints: string[] = [];
138
102
 
139
- public readonly mediaDevices: MediaDevice[] = [];
140
- public readonly mediaCodecs: MediaCodecStats[] = [];
141
- public readonly userMediaErrors: string[] = [];
103
+ public readonly mediaDevices: MetaData.MediaDeviceInfo[] = [];
142
104
  public issues: ClientIssue[] = [];
143
105
 
144
- public ωpendingPeerConnectionTimestamps: PendingPeerConnectionTimestamp[] = [];
145
- public ωpendingMediaTrackTimestamps: PendingMediaTrackTimestamp[] = [];
146
-
147
- public constructor(
148
- private readonly _model: ObservedClientModel,
149
- public readonly call: ObservedCall,
150
- public readonly appData: AppData
151
- ) {
106
+ public constructor(settings: ObservedClientSettings<AppData>, public readonly call: ObservedCall) {
152
107
  super();
153
108
  this.setMaxListeners(Infinity);
154
- }
155
-
156
- public getSfu<T extends Record<string, unknown> = Record<string, unknown>>(): ObservedSfu<T> | undefined {
157
- const sfu = this.call.observer.observedSfus.get(this.sfuId ?? '');
158
-
159
- if (!sfu) return;
160
-
161
- return sfu as ObservedSfu<T>;
162
- }
163
-
164
- public get joined() {
165
- return this._joined;
166
- }
167
109
 
168
- public set joined(value: number | undefined) {
169
- this._joined = value;
170
-
171
- if (this._joined) {
172
- this.emit('joined');
173
- }
110
+ this.clientId = settings.clientId;
111
+ this.appData = settings.appData;
112
+ this.detectors = new Detectors();
174
113
  }
175
114
 
176
- public get left() {
177
- return this._left;
115
+ public get score() {
116
+ return this.calculatedScore.value;
178
117
  }
179
118
 
180
- public set left(value: number | undefined) {
181
- this._left = value;
182
-
183
- if (this._left) {
184
- this.emit('left');
185
- }
186
- }
187
-
188
- public get sfu(): ObservedSfu | undefined {
189
- if (!this.sfuId) return;
190
-
191
- return this.call.observer.observedSfus.get(this.sfuId);
192
- }
193
-
194
- public get coordinates() {
195
- return this._model.coordinates;
196
- }
197
-
198
- public set coordinates(value: { latitude: number; longitude: number } | undefined) {
199
- this._model.coordinates = value;
200
- }
201
-
202
- public get clientId(): string {
203
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
- return this._model.clientId!;
205
- }
206
-
207
- public get roomId() {
208
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
209
- return this.call.roomId!;
210
- }
211
-
212
- public get serviceId() {
213
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
214
- return this.call.serviceId!;
215
- }
216
-
217
- public get callId() {
218
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219
- return this.call.callId!;
220
- }
221
-
222
- public get mediaUnitId() {
223
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
224
- return this._model.mediaUnitId!;
225
- }
226
-
227
- public get reports() {
228
- return this.call.reports;
229
- }
230
-
231
- public get userId() {
232
- return this._model.userId;
233
- }
234
-
235
- public set userId(userId: string | undefined) {
236
- this._model.userId = userId;
237
- }
238
-
239
- public get timeZoneOffsetInHours() {
240
- return this._timeZoneOffsetInHours;
241
- }
242
-
243
- public get marker() {
244
- return this._model.marker;
245
- }
246
-
247
- public get browser() {
248
- return this._model.browser;
249
- }
250
-
251
- public get engine() {
252
- return this._model.engine;
253
- }
254
-
255
- public get operationSystem() {
256
- return this._model.operationSystem;
257
- }
258
-
259
- public get platform() {
260
- return this._model.platform;
261
- }
262
-
263
- public get acceptedSamples() {
264
- return this._acceptedSamples;
265
- }
266
-
267
- public get closed() {
268
- return this._closed;
269
- }
270
-
271
- public get peerConnections(): ReadonlyMap<string, ObservedPeerConnection> {
272
- return this._peerConnections;
273
- }
274
-
275
119
  public close() {
276
- if (this._closed) return;
277
- this._closed = true;
120
+ if (this.closed) return;
121
+ this.closed = true;
278
122
 
279
- Array.from(this._peerConnections.values()).forEach((peerConnection) => peerConnection.close());
123
+ Array.from(this.observedPeerConnections.values()).forEach((peerConnection) => peerConnection.close());
280
124
 
281
125
  this.emit('close');
282
126
  }
283
127
 
284
- public addEventReport(params: PartialBy<Omit<CallEventReport, 'serviceId' | 'roomId' | 'callId' | 'clientId' | 'userId' | 'marker' | 'attachments'>, 'timestamp'> & { attachments?: Record<string, unknown> }) {
285
- const {
286
- attachments,
287
- ...fields
288
- } = params;
289
-
290
- this.reports.addCallEventReport({
291
- ...fields,
292
- serviceId: this.serviceId,
293
- mediaUnitId: this.mediaUnitId,
294
- roomId: this.roomId,
295
- callId: this.callId,
296
- clientId: this.clientId,
297
- userId: this.userId,
298
- timestamp: params.timestamp ?? Date.now(),
299
- marker: this.marker,
300
- attachments: attachments ? JSON.stringify(attachments) : undefined,
301
- });
302
- }
303
-
304
- public addExtensionStatsReport(extensionType: string, payload?: Record<string, unknown>) {
305
- this.reports.addClientExtensionReport({
306
- serviceId: this.serviceId,
307
- mediaUnitId: this.mediaUnitId,
308
- roomId: this.roomId,
309
- callId: this.callId,
310
- clientId: this.clientId,
311
- userId: this.userId,
312
- timestamp: Date.now(),
313
- extensionType,
314
- payload: JSON.stringify(payload),
315
- marker: this.marker,
316
- });
317
- }
318
-
319
- public addIssue(issue: ClientIssue) {
320
- try {
321
-
322
- this.reports.addCallEventReport({
323
- serviceId: this.serviceId,
324
- mediaUnitId: this.mediaUnitId,
325
- roomId: this.roomId,
326
- callId: this.callId,
327
- clientId: this.clientId,
328
- userId: this.userId,
329
-
330
- name: CallEventType.CLIENT_ISSUE,
331
- value: issue.severity,
332
- peerConnectionId: issue.peerConnectionId,
333
- mediaTrackId: issue.trackId,
334
- message: issue.description,
335
- timestamp: issue.timestamp ?? Date.now(),
336
- attachments: issue.attachments ? JSON.stringify(issue.attachments): undefined,
337
- });
338
- } catch (err) {
339
- logger.warn(`Error adding client issue: ${(err as Error)?.message}`);
340
- }
341
-
342
- this._addAndEmitIssue(issue);
343
- }
344
-
345
128
  public accept(sample: ClientSample): void {
346
- if (this._closed) throw new Error(`Client ${this.clientId} is closed`);
347
- if (sample.clientId && sample.clientId !== 'NULL' && sample.clientId !== this.clientId) {
348
- throw new Error(`Sample client id (${sample.clientId}) does not match the client id of the observed client (${this.clientId})`);
349
- }
350
- const now = Date.now();
351
-
352
- ++this._acceptedSamples;
353
- if (0 < this.issues.length) {
354
- // we reset the issues every time we accept a new sample
355
- this.issues = [];
356
- }
357
- for (const peerConnection of this._peerConnections.values()) {
358
- if (peerConnection.closed) continue;
359
- peerConnection.resetMetrics();
360
- }
361
-
362
- if (this._model.userId) {
363
- if (sample.userId && sample.userId !== 'NULL' && sample.userId !== this._model.userId) {
364
- this._model.userId = sample.userId;
365
- }
366
- } else if (sample.userId && sample.userId !== 'NULL') {
367
- this._model.userId = sample.userId;
368
- }
369
-
370
- if (this._model.marker !== sample.marker) {
371
- this._model.marker = sample.marker;
372
- this._peerConnections.forEach((peerConnection) => (peerConnection.marker = sample.marker));
373
- }
374
-
375
- if (this._timeZoneOffsetInHours !== sample.timeZoneOffsetInHours) {
376
- this._timeZoneOffsetInHours = sample.timeZoneOffsetInHours;
377
- }
129
+ if (this.closed) throw new Error(`Client ${this.clientId} is closed`);
378
130
 
379
- if (sample.os && (
380
- this._model.operationSystem?.name !== sample.os.name ||
381
- this._model.operationSystem?.version !== sample.os.version ||
382
- this._model.operationSystem?.versionName !== sample.os.versionName
383
- )) {
384
- this._model.operationSystem = sample.os;
385
-
386
- const callMetaReport = createCallMetaReport(
387
- this.serviceId,
388
- this.mediaUnitId,
389
- this.roomId,
390
- this.callId,
391
- this.clientId,
392
- {
393
- type: CallMetaType.OPERATION_SYSTEM,
394
- payload: sample.os,
395
- },
396
- this.userId,
397
- undefined,
398
- sample.timestamp
399
- );
400
-
401
- this.reports.addCallMetaReport(callMetaReport);
402
- }
403
-
404
- if (sample.engine && (
405
- this._model.engine?.name !== sample.engine.name ||
406
- this._model.engine?.version !== sample.engine.version
407
- )) {
408
- this._model.engine = sample.engine;
409
-
410
- const callMetaReport = createCallMetaReport(
411
- this.serviceId,
412
- this.mediaUnitId,
413
- this.roomId,
414
- this.callId,
415
- this.clientId,
416
- {
417
- type: CallMetaType.ENGINE,
418
- payload: sample.engine,
419
- },
420
- this.userId,
421
- undefined,
422
- sample.timestamp
423
- );
424
-
425
- this.reports.addCallMetaReport(callMetaReport);
426
- }
427
-
428
- if (sample.platform && (
429
- this._model.platform?.model !== sample.platform.model ||
430
- this._model.platform?.type !== sample.platform.type ||
431
- this._model.platform?.vendor !== sample.platform.vendor
432
- )) {
433
- this._model.platform = sample.platform;
434
-
435
- const callMetaReport = createCallMetaReport(
436
- this.serviceId,
437
- this.mediaUnitId,
438
- this.roomId,
439
- this.callId,
440
- this.clientId,
441
- {
442
- type: CallMetaType.PLATFORM,
443
- payload: sample.platform,
444
- },
445
- this.userId,
446
- undefined,
447
- sample.timestamp
448
- );
449
-
450
- this.reports.addCallMetaReport(callMetaReport);
451
- }
452
-
453
- if (sample.browser && (
454
- this._model.browser?.name !== sample.browser.name ||
455
- this._model.browser?.version !== sample.browser.version
456
- )) {
457
- this._model.browser = sample.browser;
458
-
459
- const callMetaReport = createCallMetaReport(
460
- this.serviceId,
461
- this.mediaUnitId,
462
- this.roomId,
463
- this.callId,
464
- this.clientId,
465
- {
466
- type: CallMetaType.BROWSER,
467
- payload: sample.browser,
468
- },
469
- this.userId,
470
- undefined,
471
- sample.timestamp
472
- );
473
-
474
- this.reports.addCallMetaReport(callMetaReport);
475
- }
476
-
477
- for (const mediaConstraint of sample.mediaConstraints ?? []) {
478
- const callMetaReport = createCallMetaReport(
479
- this.serviceId,
480
- this.mediaUnitId,
481
- this.roomId,
482
- this.callId,
483
- this.clientId,
484
- {
485
- type: CallMetaType.MEDIA_CONSTRAINT,
486
- payload: mediaConstraint,
487
- },
488
- this.userId,
489
- undefined,
490
- sample.timestamp
491
- );
492
-
493
- this.reports.addCallMetaReport(callMetaReport);
494
- this.mediaConstraints.push(mediaConstraint);
495
- }
496
-
497
- for (const localSDP of sample.localSDPs ?? []) {
498
- const callMetaReport = createCallMetaReport(
499
- this.serviceId,
500
- this.mediaUnitId,
501
- this.roomId,
502
- this.callId,
503
- this.clientId,
504
- {
505
- type: CallMetaType.LOCAL_SDP,
506
- payload: localSDP,
507
- },
508
- this.userId,
509
- undefined,
510
- sample.timestamp
511
- );
512
-
513
- this.reports.addCallMetaReport(callMetaReport);
514
- }
515
-
516
- for (const extensionStats of sample.extensionStats ?? []) {
517
- this.reports.addClientExtensionReport({
518
- serviceId: this.serviceId,
519
- mediaUnitId: this.mediaUnitId,
520
- roomId: this.roomId,
521
- callId: this.callId,
522
- clientId: this.clientId,
523
- userId: this.userId,
524
- timestamp: now,
525
- payload: extensionStats.payload,
526
- extensionType: extensionStats.type,
527
- });
528
- }
529
-
530
- for (const { timestamp, ...callEvent } of sample.customCallEvents ?? []) {
531
- switch (callEvent.name) {
532
- case CallEventType.CLIENT_JOINED: {
533
- const lastJoined = this.joined;
534
-
535
- this.joined = timestamp;
536
-
537
- // if it is joined before and it is joined again
538
- if (lastJoined && this.joined && lastJoined !== this.joined) {
539
- this.emit('rejoined', { lastJoined });
540
- }
541
- // in case we have a left event before the joined event
542
- if (this.left && this.joined && this.left < this.joined) {
543
- this.left = undefined;
544
- }
545
- break;
546
- }
547
- case CallEventType.CLIENT_LEFT: {
548
- this.left = timestamp;
549
- break;
550
- }
551
- case CallEventType.PEER_CONNECTION_OPENED: {
552
- callEvent.peerConnectionId && this.ωpendingPeerConnectionTimestamps.push({
553
- type: 'opened',
554
- peerConnectionId: callEvent.peerConnectionId,
555
- timestamp: timestamp ?? sample.timestamp,
556
- });
557
- break;
558
- }
559
- case CallEventType.PEER_CONNECTION_CLOSED: {
560
- callEvent.peerConnectionId && this.ωpendingPeerConnectionTimestamps.push({
561
- type: 'closed',
562
- peerConnectionId: callEvent.peerConnectionId,
563
- timestamp: timestamp ?? sample.timestamp,
564
- });
565
- break;
566
- }
567
- case CallEventType.MEDIA_TRACK_ADDED: {
568
- callEvent.peerConnectionId && callEvent.mediaTrackId && this.ωpendingMediaTrackTimestamps.push({
569
- type: 'added',
570
- peerConnectionId: callEvent.peerConnectionId,
571
- mediaTrackId: callEvent.mediaTrackId,
572
- timestamp: timestamp ?? sample.timestamp,
573
- });
574
- break;
575
- }
576
- case CallEventType.MEDIA_TRACK_REMOVED: {
577
- callEvent.peerConnectionId && callEvent.mediaTrackId && this.ωpendingMediaTrackTimestamps.push({
578
- type: 'removed',
579
- peerConnectionId: callEvent.peerConnectionId,
580
- mediaTrackId: callEvent.mediaTrackId,
581
- timestamp: timestamp ?? sample.timestamp,
582
- });
583
- break;
584
- }
585
- case CallEventType.CLIENT_ISSUE: {
586
- const severity = callEvent.value ? callEvent.value as ClientIssue['severity'] : 'minor';
587
-
588
- try {
589
- const issue: ClientIssue = {
590
- severity,
591
- timestamp: timestamp ?? Date.now(),
592
- description: callEvent.message,
593
- peerConnectionId: callEvent.peerConnectionId,
594
- trackId: callEvent.mediaTrackId,
595
- attachments: callEvent.attachments ? JSON.parse(callEvent.attachments) : undefined,
596
- };
597
-
598
- this._addAndEmitIssue(issue);
599
- } catch (err) {
600
- logger.warn(`Error parsing client issue: ${(err as Error)?.message}`);
601
- }
602
- break;
603
- }
604
- }
605
-
606
- this.reports.addCallEventReport({
607
- serviceId: this.serviceId,
608
- mediaUnitId: this.mediaUnitId,
609
- roomId: this.roomId,
610
- callId: this.callId,
611
- clientId: this.clientId,
612
- userId: this.userId,
613
- timestamp: timestamp ?? now,
614
- ...callEvent,
615
- });
616
-
617
- this.call.emit('callevent', {
618
- mediaUnitId: this.mediaUnitId,
619
- clientId: this.clientId,
620
- userId: this.userId,
621
- timestamp: timestamp ?? now,
622
- ...callEvent,
623
- });
624
-
625
- }
626
-
627
- for (const userMediaError of sample.userMediaErrors ?? []) {
628
- const callMetaReport = createCallMetaReport(
629
- this.serviceId,
630
- this.mediaUnitId,
631
- this.roomId,
632
- this.callId,
633
- this.clientId, {
634
- type: CallMetaType.USER_MEDIA_ERROR,
635
- payload: userMediaError,
636
- },
637
- this.userId,
638
- );
639
-
640
- this.reports.addCallMetaReport(callMetaReport);
641
- this.userMediaErrors.push(userMediaError);
642
- this.emit('usermediaerror', userMediaError);
643
- }
131
+ const now = Date.now();
644
132
 
645
- for (const certificate of sample.certificates ?? []) {
646
- const callMetaReport = createCallMetaReport(
647
- this.serviceId,
648
- this.mediaUnitId,
649
- this.roomId,
650
- this.callId,
651
- this.clientId, {
652
- type: CallMetaType.CERTIFICATE,
653
- payload: certificate,
654
- },
655
- this.userId
656
- );
133
+ sample.clientEvents?.forEach(this._processClientEvent.bind(this));
134
+ sample.clientMetaItems?.forEach(this.processMetadata.bind(this));
135
+ sample.clientIssues?.forEach(this.processIssue.bind(this));
136
+ sample.peerConnections?.forEach(this._updatePeerConnection.bind(this));
137
+ sample.extensionStats?.forEach(this.processExtensionStats.bind(this));
657
138
 
658
- this.reports.addCallMetaReport(callMetaReport);
659
- }
660
-
661
- for (const codec of sample.codecs ?? []) {
662
- const callMetaReport = createCallMetaReport(
663
- this.serviceId,
664
- this.mediaUnitId,
665
- this.roomId,
666
- this.callId,
667
- this.clientId, {
668
- type: CallMetaType.CODEC,
669
- payload: codec,
670
- }, this.userId);
139
+ // emit new attachments?
140
+ this.attachments = sample.attachments;
671
141
 
672
- this.reports.addCallMetaReport(callMetaReport);
673
- if (codec.mimeType && !this.mediaCodecs.find((c) => c.mimeType === codec.mimeType)) {
674
- this.mediaCodecs.push(codec);
675
- }
676
- }
677
-
678
- for (const iceServer of sample.iceServers ?? []) {
679
- const callMetaReport = createCallMetaReport(
680
- this.serviceId,
681
- this.mediaUnitId,
682
- this.roomId,
683
- this.callId,
684
- this.clientId, {
685
- type: CallMetaType.ICE_SERVER,
686
- payload: iceServer,
687
- }, this.userId);
142
+ this.calculatedScore.value = sample.score;
143
+ this.detectors.update();
688
144
 
689
- this.reports.addCallMetaReport(callMetaReport);
690
- }
691
-
692
- for (const mediaDevice of sample.mediaDevices ?? []) {
693
- const callMetaReport = createCallMetaReport(
694
- this.serviceId,
695
- this.mediaUnitId,
696
- this.roomId,
697
- this.callId,
698
- this.clientId, {
699
- type: CallMetaType.MEDIA_DEVICE,
700
- payload: mediaDevice,
701
- }, this.userId);
702
-
703
- this.reports.addCallMetaReport(callMetaReport);
704
- if (mediaDevice.id && !this.mediaDevices.find((d) => d.id === mediaDevice.id)) {
705
- this.mediaDevices.push(mediaDevice);
706
- }
707
- }
708
-
709
- for (const mediaSource of sample.mediaSources ?? []) {
710
- const callMetaReport = createCallMetaReport(
711
- this.serviceId,
712
- this.mediaUnitId,
713
- this.roomId,
714
- this.callId,
715
- this.clientId, {
716
- type: CallMetaType.MEDIA_SOURCE,
717
- payload: mediaSource,
718
- }, this.userId);
719
-
720
- this.reports.addCallMetaReport(callMetaReport);
721
- }
722
-
723
- for (const transport of sample.pcTransports ?? []) {
724
- try {
725
- const peerConnection = this._peerConnections.get(transport.transportId) ?? this._createPeerConnection(transport.peerConnectionId);
145
+ // emit update
146
+ this.emit('update', {
147
+ sample,
148
+ elapsedTimeInMs: now - this.updated,
149
+ });
150
+ this.updated = now;
151
+ }
726
152
 
727
- !peerConnection.label && transport.label && (peerConnection.label = transport.label);
728
- peerConnection.update(transport, sample.timestamp);
729
- } catch (err) {
730
- logger.error(`Error creating peer connection: ${(err as Error)?.message}`);
153
+ private _processClientEvent(event: ClientEvent) {
154
+ switch (event.type) {
155
+ case ClientEventTypes.CALL_STARTED: {
156
+ // update call timestamp if it's the first time
157
+ break;
731
158
  }
732
-
733
- }
734
-
735
- for (const track of sample.inboundAudioTracks ?? []) {
736
- if (!track.peerConnectionId || !track.trackId) {
737
- logger.warn(`InboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
738
-
739
- continue;
159
+ case ClientEventTypes.CALL_ENDED: {
160
+ // update call timestamp if it's the first time
161
+ break;
740
162
  }
741
-
742
- try {
743
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
744
-
745
- if (!peerConnection) continue;
746
-
747
- const inboundAudioTrack = peerConnection.inboundAudioTracks.get(track.trackId) ?? peerConnection.createInboundAudioTrack({
748
- trackId: track.trackId,
749
- sfuStreamId: track.sfuStreamId,
750
- sfuSinkId: track.sfuSinkId,
751
- });
752
-
753
- inboundAudioTrack.update(track, sample.timestamp);
754
-
755
- if (!inboundAudioTrack.remoteOutboundTrack) {
756
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundAudioTrack.get(inboundAudioTrack.sfuStreamId ?? '');
757
-
758
- if (remoteOutboundTrack) {
759
- inboundAudioTrack.remoteOutboundTrack = remoteOutboundTrack;
760
-
761
- inboundAudioTrack.once('close', () => {
762
- remoteOutboundTrack?.remoteInboundTracks.delete(inboundAudioTrack.trackId ?? '');
763
- });
764
- remoteOutboundTrack.remoteInboundTracks.set(inboundAudioTrack.trackId ?? '', inboundAudioTrack);
163
+ case ClientEventTypes.CLIENT_JOINED: {
164
+ if (event.timestamp) {
165
+ if (!this.joinedAt) {
166
+ this.joinedAt = event.timestamp;
167
+ this.emit('joined');
168
+ } else if (this.joinedAt < event.timestamp) {
169
+ this.emit('rejoined', event.timestamp);
170
+ } else {
171
+ this.joinedAt = event.timestamp;
172
+ logger.warn(`Client ${this.clientId} joinedAt timestamp was updated to ${event.timestamp}. the joined event will not be emitted.`);
765
173
  }
766
174
  }
767
- } catch (err) {
768
- logger.error(`Error creating inbound audio track: ${(err as Error)?.message}`);
769
- }
770
- }
771
-
772
- for (const track of sample.inboundVideoTracks ?? []) {
773
- if (!track.peerConnectionId || !track.trackId) {
774
- logger.warn(`InboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
775
-
776
- continue;
777
- }
778
- if (isValidUuid(track.trackId) === false) {
779
- // mediasoup-probator trackId is not a valid UUID, no need to warn about it
780
- if (track.trackId === 'probator') continue;
781
- logger.warn(`InboundVideoTrack with invalid trackId: ${track.trackId}`);
782
- continue;
175
+ break;
783
176
  }
784
-
785
- try {
786
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
787
-
788
- if (!peerConnection) continue;
789
-
790
- const inboundVideoTrack = peerConnection.inboundVideoTracks.get(track.trackId) ?? peerConnection.createInboundVideoTrack({
791
- trackId: track.trackId,
792
- sfuStreamId: track.sfuStreamId,
793
- sfuSinkId: track.sfuSinkId,
794
- });
795
-
796
- inboundVideoTrack.update(track, sample.timestamp);
797
-
798
- if (!inboundVideoTrack.remoteOutboundTrack) {
799
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundVideoTrack.get(inboundVideoTrack.sfuStreamId ?? '');
800
-
801
- if (remoteOutboundTrack) {
802
- inboundVideoTrack.remoteOutboundTrack = remoteOutboundTrack;
803
-
804
- inboundVideoTrack.once('close', () => {
805
- remoteOutboundTrack?.remoteInboundTracks.delete(inboundVideoTrack.trackId ?? '');
806
- });
807
- remoteOutboundTrack.remoteInboundTracks.set(inboundVideoTrack.trackId ?? '', inboundVideoTrack);
177
+ case ClientEventTypes.CLIENT_LEFT: {
178
+ if (event.timestamp) {
179
+ if (!this.leftAt) {
180
+ this.leftAt = event.timestamp;
181
+ this.emit('left');
808
182
  }
809
183
  }
810
- } catch (err) {
811
- logger.error(`Error creating inbound video track: ${(err as Error)?.message}`);
812
- }
813
- }
814
-
815
- for (const track of sample.outboundAudioTracks ?? []) {
816
- if (!track.peerConnectionId || !track.trackId) {
817
- logger.warn(`OutboundAudioTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
818
-
819
- continue;
820
- }
821
-
822
- try {
823
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
824
-
825
- if (!peerConnection) continue;
826
-
827
- const outboundAudioTrack = peerConnection.outboundAudioTracks.get(track.trackId) ?? peerConnection.createOutboundAudioTrack({
828
- trackId: track.trackId,
829
- sfuStreamId: track.sfuStreamId,
830
- });
831
-
832
- outboundAudioTrack.update(track, sample.timestamp);
833
-
834
- if (outboundAudioTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundAudioTrack.has(outboundAudioTrack.sfuStreamId)) {
835
- this.call.sfuStreamIdToOutboundAudioTrack.set(outboundAudioTrack.sfuStreamId, outboundAudioTrack);
836
- }
837
-
838
- } catch (err) {
839
- logger.error(`Error creating outbound audio track: ${(err as Error)?.message}`);
184
+ break;
840
185
  }
841
-
842
186
  }
843
187
 
844
- for (const track of sample.outboundVideoTracks ?? []) {
845
- if (!track.peerConnectionId || !track.trackId) {
846
- logger.warn(`OutboundVideoTrack without peerConnectionId or trackId: ${JSON.stringify(track)}`);
847
-
848
- continue;
849
- }
850
-
851
- try {
852
- const peerConnection = this._peerConnections.get(track.peerConnectionId) ?? this._createPeerConnection(track.peerConnectionId);
853
-
854
- if (!peerConnection) continue;
855
-
856
- const outboundVideoTrack = peerConnection.outboundVideoTracks.get(track.trackId) ?? peerConnection.createOutboundVideoTrack({
857
- trackId: track.trackId,
858
- sfuStreamId: track.sfuStreamId,
859
- });
860
-
861
- outboundVideoTrack.update(track, sample.timestamp);
862
-
863
- if (outboundVideoTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundVideoTrack.has(outboundVideoTrack.sfuStreamId)) {
864
- this.call.sfuStreamIdToOutboundVideoTrack.set(outboundVideoTrack.sfuStreamId, outboundVideoTrack);
865
- }
866
-
867
- } catch (err) {
868
- logger.error(`Error creating outbound video track: ${(err as Error)?.message}`);
869
- }
870
- }
871
-
872
- for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
873
- if (!iceLocalCandidate.peerConnectionId) {
874
- logger.warn(`Local ice candidate without peerConnectionId: ${JSON.stringify(iceLocalCandidate)}`);
875
- continue;
876
- }
877
- const peerConnection = this._peerConnections.get(iceLocalCandidate.peerConnectionId);
878
-
879
- if (!peerConnection) {
880
- logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
881
- continue;
882
- }
883
-
884
- peerConnection.ICE.addLocalCandidate(iceLocalCandidate, sample.timestamp);
885
- }
886
-
887
- for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
888
- if (!iceRemoteCandidate.peerConnectionId) {
889
- logger.warn(`Remote ice candidate without peerConnectionId: ${JSON.stringify(iceRemoteCandidate)}`);
890
- continue;
891
- }
892
- const peerConnection = this._peerConnections.get(iceRemoteCandidate.peerConnectionId);
893
-
894
- if (!peerConnection) {
895
- logger.debug(`Peer connection ${iceRemoteCandidate.peerConnectionId} not found for ice remote candidate ${iceRemoteCandidate.id}`);
896
- continue;
897
- }
898
-
899
- peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate, sample.timestamp);
900
- }
901
-
902
- for (const candidatePair of sample.iceCandidatePairs ?? []) {
903
- const peerConnection = this._peerConnections.get(candidatePair.peerConnectionId);
904
-
905
- if (!peerConnection) {
906
- logger.debug(`Peer connection ${candidatePair.peerConnectionId} not found for ice candidate pair ${candidatePair.localCandidateId}, ${candidatePair.remoteCandidateId}`);
907
-
908
- continue;
909
- }
910
-
911
- peerConnection.ICE.update(candidatePair, sample.timestamp);
912
- }
913
-
914
- for (const dataChannel of sample.dataChannels ?? []) {
915
- if (!dataChannel.peerConnectionId || !dataChannel.dataChannelIdentifier) {
916
- logger.warn(`DataChannel without peerConnectionId or dataChannelIdentifier: ${JSON.stringify(dataChannel)}`);
917
-
918
- continue;
919
- }
920
-
921
- try {
922
- const peerConnection = this._peerConnections.get(dataChannel.peerConnectionId) ?? this._createPeerConnection(dataChannel.peerConnectionId);
923
-
924
- if (!peerConnection) continue;
925
-
926
- const observedDataChannel = peerConnection.dataChannels.get(dataChannel.dataChannelIdentifier) ?? peerConnection.createDataChannel(dataChannel.dataChannelIdentifier);
927
-
928
- observedDataChannel.update(dataChannel, sample.timestamp);
929
- } catch (err) {
930
- logger.error(`Error creating data channel: ${(err as Error)?.message}`);
931
- }
932
- }
933
-
934
- // try to set the timestamps of the peer connections
935
- if (0 < this.ωpendingPeerConnectionTimestamps.length) {
936
- const newPendingTimestamps: PendingPeerConnectionTimestamp[] = [];
937
-
938
- for (const pendingTimestamp of this.ωpendingPeerConnectionTimestamps) {
939
- const peerConnection = this._peerConnections.get(pendingTimestamp.peerConnectionId);
940
-
941
- if (!peerConnection) {
942
- newPendingTimestamps.push(pendingTimestamp);
943
- continue;
944
- }
945
- if (pendingTimestamp.type === 'opened') peerConnection.opened = pendingTimestamp.timestamp;
946
- else if (pendingTimestamp.type === 'closed') peerConnection.closedTimestamp = pendingTimestamp.timestamp;
947
- }
948
-
949
- this.ωpendingPeerConnectionTimestamps = newPendingTimestamps;
950
- }
951
-
952
- // try to set the timestamps of the media tracks
953
- if (0 < this.ωpendingMediaTrackTimestamps.length) {
954
- const newPendingTimestamps: PendingMediaTrackTimestamp[] = [];
955
-
956
- for (const pendingTimestamp of this.ωpendingMediaTrackTimestamps) {
957
- const peerConnection = this._peerConnections.get(pendingTimestamp.peerConnectionId);
958
- const mediaTrack = peerConnection?.inboundAudioTracks.get(pendingTimestamp.mediaTrackId) ??
959
- peerConnection?.inboundVideoTracks.get(pendingTimestamp.mediaTrackId) ??
960
- peerConnection?.outboundAudioTracks.get(pendingTimestamp.mediaTrackId) ??
961
- peerConnection?.outboundVideoTracks.get(pendingTimestamp.mediaTrackId);
962
-
963
- if (!mediaTrack) {
964
- newPendingTimestamps.push(pendingTimestamp);
965
- continue;
966
- }
967
-
968
- if (pendingTimestamp.type === 'added') mediaTrack.added = pendingTimestamp.timestamp;
969
- else if (pendingTimestamp.type === 'removed') mediaTrack.removed = pendingTimestamp.timestamp;
970
- }
971
-
972
- this.ωpendingMediaTrackTimestamps = newPendingTimestamps;
973
- }
974
-
975
- // close resources that are not visited
976
- for (const peerConnection of this._peerConnections.values()) {
977
- if (!peerConnection.visited) {
978
- peerConnection.close();
979
-
980
- continue;
981
- }
982
-
983
- for (const track of peerConnection.inboundAudioTracks.values()) {
984
- if (!track.visited) {
985
- track.close();
986
-
987
- continue;
988
- }
989
-
990
- track.visited = false;
991
- }
992
- for (const track of peerConnection.inboundVideoTracks.values()) {
993
- if (!track.visited) {
994
- track.close();
995
-
996
- continue;
997
- }
188
+ this.call.observer.processors.events.process({
189
+ call: this.call,
190
+ client: this,
191
+ data: event,
192
+ });
193
+ }
998
194
 
999
- track.visited = false;
195
+ public processMetadata(metadata: ClientMetaData) {
196
+ switch (metadata.type) {
197
+ case ClientMetaTypes.BROWSER: {
198
+ this.browser = parseJsonAs(metadata.payload);
199
+ break;
1000
200
  }
1001
- for (const track of peerConnection.outboundAudioTracks.values()) {
1002
- if (!track.visited) {
1003
- track.close();
1004
-
1005
- continue;
1006
- }
1007
-
1008
- track.visited = false;
201
+ case ClientMetaTypes.ENGINE: {
202
+ this.engine = parseJsonAs(metadata.payload);
203
+ break;
1009
204
  }
1010
- for (const dataChannel of peerConnection.dataChannels.values()) {
1011
- if (!dataChannel.visited) {
1012
- dataChannel.close();
1013
-
1014
- continue;
1015
- }
1016
-
1017
- dataChannel.visited = false;
205
+ case ClientMetaTypes.PLATFORM: {
206
+ this.platform = parseJsonAs(metadata.payload);
207
+ break;
1018
208
  }
1019
-
1020
- peerConnection.visited = false;
1021
- }
1022
-
1023
- // update metrics
1024
- const wasUsingTURN = this.usingTURN;
1025
- const elapsedTimeInMs = now - this.updated;
1026
-
1027
- this.usingTURN = false;
1028
- this.availableIncomingBitrate = 0;
1029
- this.availableOutgoingBitrate = 0;
1030
- this.deltaInboundPacketsLost = 0;
1031
- this.deltaInboundPacketsReceived = 0;
1032
- this.deltaOutboundPacketsSent = 0;
1033
- this.deltaReceivedAudioBytes = 0;
1034
- this.deltaReceivedVideoBytes = 0;
1035
- this.deltaSentAudioBytes = 0;
1036
- this.deltaSentVideoBytes = 0;
1037
- this.deltaDataChannelBytesReceived = 0;
1038
- this.deltaDataChannelBytesSent = 0;
1039
-
1040
- this.outboundAudioBitrate = 0;
1041
- this.outboundVideoBitrate = 0;
1042
- this.inboundAudioBitrate = 0;
1043
- this.inboundVideoBitrate = 0;
1044
- let sumRttInMs = 0;
1045
- let anyPeerConnectionUsingTurn = false;
1046
-
1047
- let minPcScore: number | undefined;
1048
- let maxPcScore: number | undefined;
1049
- let numberOfScoredPeerConnections = 0;
1050
-
1051
- for (const peerConnection of this._peerConnections.values()) {
1052
- if (peerConnection.closed) continue;
1053
-
1054
- peerConnection.updateMetrics();
1055
-
1056
- this.deltaInboundPacketsLost += peerConnection.deltaInboundPacketsLost;
1057
- this.deltaInboundPacketsReceived += peerConnection.deltaInboundPacketsReceived;
1058
- this.deltaOutboundPacketsSent += peerConnection.deltaOutboundPacketsSent;
1059
- this.deltaReceivedAudioBytes += peerConnection.deltaReceivedAudioBytes;
1060
- this.deltaReceivedVideoBytes += peerConnection.deltaReceivedVideoBytes;
1061
- this.deltaSentAudioBytes += peerConnection.deltaSentAudioBytes;
1062
- this.deltaSentVideoBytes += peerConnection.deltaSentVideoBytes;
1063
- this.deltaDataChannelBytesReceived += peerConnection.deltaDataChannelBytesReceived;
1064
- this.deltaDataChannelBytesSent += peerConnection.deltaDataChannelBytesSent;
1065
-
1066
- this.availableIncomingBitrate += peerConnection.availableIncomingBitrate ?? 0;
1067
- this.availableOutgoingBitrate += peerConnection.availableOutgoingBitrate ?? 0;
1068
-
1069
- sumRttInMs += peerConnection.avgRttInMs ?? 0;
1070
-
1071
- anyPeerConnectionUsingTurn ||= peerConnection.usingTURN;
1072
-
1073
- if (peerConnection.score) {
1074
- if (minPcScore === undefined || peerConnection.score.score < minPcScore) minPcScore = peerConnection.score.score;
1075
- if (maxPcScore === undefined || peerConnection.score.score > maxPcScore) maxPcScore = peerConnection.score.score;
1076
-
1077
- ++numberOfScoredPeerConnections;
209
+ case ClientMetaTypes.OPERATION_SYSTEM: {
210
+ this.operationSystem = parseJsonAs(metadata.payload);
211
+ break;
1078
212
  }
1079
213
  }
1080
214
 
1081
- this.score = {
1082
- score: minPcScore ?? -1,
1083
- remarks: [ {
1084
- severity: 'none',
1085
- text: `Min and max score of all peer connections: ${minPcScore}, ${maxPcScore}, number of PeerConnections with scores: ${numberOfScoredPeerConnections}`,
1086
- } ],
1087
- timestamp: sample.timestamp,
1088
- };
1089
- this.emit('score', this.score);
1090
-
1091
- this.usingTURN = anyPeerConnectionUsingTurn === true;
1092
-
1093
- if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
1094
-
1095
- this.totalSentBytes += this.deltaSentAudioBytes + this.deltaSentVideoBytes;
1096
- this.totalReceivedBytes += this.deltaReceivedAudioBytes + this.deltaReceivedVideoBytes;
1097
- this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes;
1098
- this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes;
1099
- this.totalSentAudioBytes += this.deltaSentAudioBytes;
1100
- this.totalSentVideoBytes += this.deltaSentVideoBytes;
1101
- this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent;
1102
- this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived;
1103
- this.totalInboundPacketsLost += this.deltaInboundPacketsLost;
1104
- this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent;
1105
- this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived;
1106
-
1107
- this.outboundAudioBitrate = (this.deltaSentAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1108
- this.outboundVideoBitrate = (this.deltaSentVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1109
- this.inboundAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1110
- this.inboundVideoBitrate = (this.deltaReceivedVideoBytes * 8) / (Math.max(elapsedTimeInMs, 1) / 1000);
1111
-
1112
- this.avgRttInMs = this._peerConnections.size ? sumRttInMs / this._peerConnections.size : undefined;
1113
-
1114
- // to make sure when sample is emitted it can be associated to this client
1115
- sample.clientId = this.clientId;
1116
-
1117
- this.updated = now;
1118
- this.lastStatsTimestamp = sample.timestamp;
1119
- this.emit('update', {
1120
- sample,
1121
- elapsedTimeInMs,
215
+ this.call.observer.processors.metaData.process({
216
+ call: this.call,
217
+ client: this,
218
+ data: metadata,
1122
219
  });
1123
220
  }
1124
221
 
1125
- private _addAndEmitIssue(issue: ClientIssue) {
1126
- this.issues.push(issue);
222
+ public processIssue(issue: ClientIssue) {
223
+ this.call.observer.processors.issues.process({
224
+ call: this.call,
225
+ client: this,
226
+ data: issue,
227
+ });
228
+ }
1127
229
 
1128
- if (issue.peerConnectionId) {
1129
- const peerConnection = this._peerConnections.get(issue.peerConnectionId);
230
+ public processExtensionStats(stats: ExtensionStat) {
231
+ this.call.observer.processors.extensionStats.process({
232
+ call: this.call,
233
+ client: this,
234
+ data: stats,
235
+ });
236
+ }
1130
237
 
1131
- if (peerConnection) {
1132
-
1133
- if (issue.trackId) {
1134
- const track = peerConnection.inboundAudioTracks.get(issue.trackId) ?? peerConnection.inboundVideoTracks.get(issue.trackId);
238
+ private _updatePeerConnection(sample: PeerConnectionSample) {
239
+ let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId);
1135
240
 
1136
- if (track) track.ωpendingIssuesForScores.push(issue);
1137
- } else {
1138
- peerConnection.ωpendingIssuesForScores.push(issue);
1139
- }
241
+ if (!observedPeerConnection) {
242
+ if (!sample.peerConnectionId) {
243
+ return logger.warn(
244
+ `ObservedClient received an invalid PeerConnectionSample (missing peerConnectionId field). ClientId: ${this.clientId}, CallId: ${this.call.callId}`,
245
+ sample
246
+ );
1140
247
  }
1141
- }
1142
-
1143
- this.emit('issue', issue);
1144
- }
1145
248
 
1146
- private _createPeerConnection(peerConnectionId: string, label?: string): ObservedPeerConnection {
1147
- const result = new ObservedPeerConnection({
1148
- peerConnectionId,
1149
- label,
1150
- }, this);
249
+ observedPeerConnection = new ObservedPeerConnection(sample.peerConnectionId, this);
1151
250
 
1152
- const onNewSelectedCandidatePair = ({ localCandidate, remoteCandidate }: { localCandidate: IceLocalCandidate, remoteCandidate: IceRemoteCandidate }) => {
1153
- this.emit('selectedcandidatepair', {
1154
- peerConnection: result,
1155
- localCandidate,
1156
- remoteCandidate,
251
+ observedPeerConnection.once('close', () => {
252
+ this.observedPeerConnections.delete(sample.peerConnectionId);
1157
253
  });
1158
- };
1159
-
1160
- result.once('close', () => {
1161
- result.ICE.off('new-selected-candidate-pair', onNewSelectedCandidatePair);
1162
- this._peerConnections.delete(peerConnectionId);
1163
- });
1164
- result.ICE.on('new-selected-candidate-pair', onNewSelectedCandidatePair);
1165
- this._peerConnections.set(peerConnectionId, result);
1166
-
1167
- this.emit('newpeerconnection', result);
254
+ this.observedPeerConnections.set(sample.peerConnectionId, observedPeerConnection);
255
+ }
1168
256
 
1169
- return result;
257
+ observedPeerConnection.accept(sample);
1170
258
  }
1171
- }
259
+ }