@observertc/observer-js 0.41.7 → 0.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/lib/ObservedCall.d.ts +10 -3
  2. package/lib/ObservedCall.d.ts.map +1 -1
  3. package/lib/ObservedCall.js +28 -0
  4. package/lib/ObservedClient.d.ts +5 -0
  5. package/lib/ObservedClient.d.ts.map +1 -1
  6. package/lib/ObservedClient.js +83 -32
  7. package/lib/ObservedInboundAudioTrack.d.ts +94 -0
  8. package/lib/ObservedInboundAudioTrack.d.ts.map +1 -0
  9. package/lib/ObservedInboundAudioTrack.js +215 -0
  10. package/lib/ObservedInboundVideoTrack.d.ts +98 -0
  11. package/lib/ObservedInboundVideoTrack.d.ts.map +1 -0
  12. package/lib/{ObservedInboundTrack.js → ObservedInboundVideoTrack.js} +53 -44
  13. package/lib/ObservedOutboundAudioTrack.d.ts +85 -0
  14. package/lib/ObservedOutboundAudioTrack.d.ts.map +1 -0
  15. package/lib/{ObservedOutboundTrack.js → ObservedOutboundAudioTrack.js} +35 -58
  16. package/lib/ObservedOutboundVideoTrack.d.ts +88 -0
  17. package/lib/ObservedOutboundVideoTrack.d.ts.map +1 -0
  18. package/lib/ObservedOutboundVideoTrack.js +204 -0
  19. package/lib/ObservedPeerConnection.d.ts +26 -16
  20. package/lib/ObservedPeerConnection.d.ts.map +1 -1
  21. package/lib/ObservedPeerConnection.js +157 -86
  22. package/lib/Observer.d.ts +1 -0
  23. package/lib/Observer.d.ts.map +1 -1
  24. package/lib/Observer.js +2 -2
  25. package/lib/RemoteTrackAssigner.d.ts +14 -0
  26. package/lib/RemoteTrackAssigner.d.ts.map +1 -0
  27. package/lib/RemoteTrackAssigner.js +52 -0
  28. package/lib/common/CalculatedScore.d.ts +79 -0
  29. package/lib/common/CalculatedScore.d.ts.map +1 -0
  30. package/lib/common/CalculatedScore.js +155 -0
  31. package/lib/common/QualityScoreUtils.d.ts +1 -0
  32. package/lib/common/QualityScoreUtils.d.ts.map +1 -0
  33. package/lib/common/QualityScoreUtils.js +1 -0
  34. package/lib/common/utils.d.ts +1 -0
  35. package/lib/common/utils.d.ts.map +1 -1
  36. package/lib/common/utils.js +14 -1
  37. package/lib/index.d.ts +4 -2
  38. package/lib/index.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/ObservedCall.ts +40 -4
  41. package/src/ObservedClient.ts +79 -17
  42. package/src/ObservedInboundAudioTrack.ts +336 -0
  43. package/src/{ObservedInboundTrack.ts → ObservedInboundVideoTrack.ts} +85 -66
  44. package/src/{ObservedOutboundTrack.ts → ObservedOutboundAudioTrack.ts} +59 -74
  45. package/src/ObservedOutboundVideoTrack.ts +326 -0
  46. package/src/ObservedPeerConnection.ts +205 -131
  47. package/src/Observer.ts +3 -2
  48. package/src/RemoteTrackAssigner.ts +68 -0
  49. package/src/common/CalculatedScore.ts +180 -0
  50. package/src/common/QualityScoreUtils.ts +0 -0
  51. package/src/common/utils.ts +17 -1
  52. package/src/index.ts +4 -2
  53. package/src/monitors/CallSummaryMonitor.ts +3 -3
  54. package/lib/ObservedInboundTrack.d.ts +0 -96
  55. package/lib/ObservedInboundTrack.d.ts.map +0 -1
  56. package/lib/ObservedOutboundTrack.d.ts +0 -84
  57. package/lib/ObservedOutboundTrack.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QualityScoreUtils.d.ts","sourceRoot":"","sources":["../../src/common/QualityScoreUtils.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ "use strict";
@@ -5,4 +5,5 @@ export type Writable<T> = T extends object ? {
5
5
  export declare function iteratorConverter<T>(generator: Generator<T, void, undefined>): IterableIterator<T>;
6
6
  export declare function asyncIteratorConverter<T>(generator: AsyncGenerator<T, void, undefined>): AsyncIterableIterator<T>;
7
7
  export declare function isValidUuid(str: string): boolean;
8
+ export declare function getMedian(arr: number[]): number;
8
9
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/common/utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAE/E,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG;IAAE,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,CAAC,CAAC;AAE9F,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CASlG;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CASjH;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIhD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/common/utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAE/E,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG;IAAE,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,CAAC,CAAC;AAE9F,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CASlG;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CASjH;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIhD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,CAc/C"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isValidUuid = exports.asyncIteratorConverter = exports.iteratorConverter = void 0;
3
+ exports.getMedian = exports.isValidUuid = exports.asyncIteratorConverter = exports.iteratorConverter = void 0;
4
4
  function iteratorConverter(generator) {
5
5
  return {
6
6
  [Symbol.iterator]() {
@@ -28,3 +28,16 @@ function isValidUuid(str) {
28
28
  return regexp.test(str);
29
29
  }
30
30
  exports.isValidUuid = isValidUuid;
31
+ function getMedian(arr) {
32
+ // Sort the array in ascending order
33
+ const sortedArr = arr.slice().sort((a, b) => a - b);
34
+ // Calculate the middle index
35
+ const mid = Math.floor(sortedArr.length / 2);
36
+ // If the array length is odd, return the middle element
37
+ if (sortedArr.length % 2 !== 0) {
38
+ return sortedArr[mid];
39
+ }
40
+ // If the array length is even, return the average of the two middle elements
41
+ return (sortedArr[mid - 1] + sortedArr[mid]) / 2;
42
+ }
43
+ exports.getMedian = getMedian;
package/lib/index.d.ts CHANGED
@@ -9,8 +9,10 @@ export type { ClientIssueMonitorConfig, ClientIssueMonitorEmittedIssueEvent, Cli
9
9
  export type { ObservedCall, ObservedCallModel } from './ObservedCall';
10
10
  export type { ObservedClient, ObservedClientModel } from './ObservedClient';
11
11
  export type { ObservedPeerConnection, ObservedPeerConnectionModel, ObservedPeerConnectionEvents } from './ObservedPeerConnection';
12
- export type { ObservedInboundTrack, ObservedInboundTrackModel, ObservedInboundTrackEvents } from './ObservedInboundTrack';
13
- export type { ObservedOutboundTrack, ObservedOutboundTrackModel, ObservedOutboundTrackEvents } from './ObservedOutboundTrack';
12
+ export type { ObservedInboundAudioTrack, ObservedInboundAudioTrackModel, ObservedInboundAudioTrackEvents } from './ObservedInboundAudioTrack';
13
+ export type { ObservedOutboundAudioTrack, ObservedOutboundAudioTrackModel, ObservedOutboundAudioTrackEvents } from './ObservedOutboundAudioTrack';
14
+ export type { ObservedInboundVideoTrack, ObservedInboundVideoTrackModel, ObservedInboundVideoTrackEvents } from './ObservedInboundVideoTrack';
15
+ export type { ObservedOutboundVideoTrack, ObservedOutboundVideoTrackModel, ObservedOutboundVideoTrackEvents } from './ObservedOutboundVideoTrack';
14
16
  export type { CallSummary, ClientSummary, ClientIssue } from './monitors/CallSummary';
15
17
  export type { ObserverSinkContext } from './common/types';
16
18
  export type { SfuServerMonitorMetricsRecord } from './monitors/SfuServerMonitor';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,YAAY,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,YAAY,MAAM,+BAA+B,CAAC;AAE9D,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAExD,YAAY,EACX,OAAO,EACP,YAAY,EACZ,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,WAAW,GACX,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EACX,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,6BAA6B,EAC7B,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,EACb,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,GAClB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACX,wBAAwB,EACxB,mCAAmC,EACnC,kBAAkB,EAClB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,YAAY,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AAClI,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAC1H,YAAY,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC9H,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACtF,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACjF,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAClH,OAAO,EACN,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mCAAmC,EACnC,kCAAkC,EAClC,oCAAoC,GACpC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtD,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,YAE9D;AAED,OAAO,EAAE,QAAQ,EAAE,MAAM,EAA8B,MAAM,iBAAiB,CAAC;AAC/E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAG3D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,YAAY,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,YAAY,MAAM,+BAA+B,CAAC;AAE9D,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAExD,YAAY,EACX,OAAO,EACP,YAAY,EACZ,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,WAAW,GACX,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EACX,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,6BAA6B,EAC7B,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,EACb,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,GAClB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACX,wBAAwB,EACxB,mCAAmC,EACnC,kBAAkB,EAClB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,YAAY,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AAClI,YAAY,EAAE,yBAAyB,EAAE,8BAA8B,EAAE,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAC9I,YAAY,EAAE,0BAA0B,EAAE,+BAA+B,EAAE,gCAAgC,EAAE,MAAM,8BAA8B,CAAC;AAClJ,YAAY,EAAE,yBAAyB,EAAE,8BAA8B,EAAE,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAC9I,YAAY,EAAE,0BAA0B,EAAE,+BAA+B,EAAE,gCAAgC,EAAE,MAAM,8BAA8B,CAAC;AAClJ,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACtF,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACjF,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAClH,OAAO,EACN,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,mCAAmC,EACnC,kCAAkC,EAClC,oCAAoC,GACpC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEpG,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtD,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,YAE9D;AAED,OAAO,EAAE,QAAQ,EAAE,MAAM,EAA8B,MAAM,iBAAiB,CAAC;AAC/E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAG3D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@observertc/observer-js",
3
- "version": "0.41.7",
3
+ "version": "0.42.1",
4
4
  "description": "Server Side NodeJS Library for processing ObserveRTC Samples",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1,13 +1,15 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import { ObservedClient, ObservedClientEvents, ObservedClientModel } from './ObservedClient';
3
- import { ObservedOutboundTrack } from './ObservedOutboundTrack';
4
3
  import { Observer } from './Observer';
5
4
  import { createClientJoinedEventReport } from './common/callEventReports';
6
- import { PartialBy } from './common/utils';
5
+ import { getMedian, PartialBy } from './common/utils';
7
6
  import { CallEventReport } from '@observertc/report-schemas-js';
8
7
  import { createProcessor } from './common/Middleware';
9
8
  import { ClientSample } from '@observertc/sample-schemas-js';
10
9
  import { ClientIssue } from './monitors/CallSummary';
10
+ import { ObservedOutboundAudioTrack } from './ObservedOutboundAudioTrack';
11
+ import { ObservedOutboundVideoTrack } from './ObservedOutboundVideoTrack';
12
+ import { CalculatedScore } from './common/CalculatedScore';
11
13
 
12
14
  type ClientIssueDetectionConfig = Pick<ClientIssue, 'severity'>;
13
15
 
@@ -38,8 +40,13 @@ export class ObservedCall<AppData extends Record<string, unknown> = Record<strin
38
40
 
39
41
  private readonly _clients = new Map<string, ObservedClient>();
40
42
 
41
- public readonly sfuStreamIdToOutboundAudioTrack = new Map<string | number, ObservedOutboundTrack<'audio'>>();
42
- public readonly sfuStreamIdToOutboundVideoTrack = new Map<string | number, ObservedOutboundTrack<'video'>>();
43
+ public _scoreData?: {
44
+ score: CalculatedScore,
45
+ calculated: number,
46
+ };
47
+
48
+ public readonly sfuStreamIdToOutboundAudioTrack = new Map<string | number, ObservedOutboundAudioTrack>();
49
+ public readonly sfuStreamIdToOutboundVideoTrack = new Map<string | number, ObservedOutboundVideoTrack>();
43
50
  private _closed = false;
44
51
  private _updated = Date.now();
45
52
  private _ended?: number;
@@ -84,6 +91,35 @@ export class ObservedCall<AppData extends Record<string, unknown> = Record<strin
84
91
  return this._clients;
85
92
  }
86
93
 
94
+ public get score(): CalculatedScore | undefined {
95
+ const now = Date.now();
96
+
97
+ if (now - 15000 < (this._scoreData?.calculated ?? 0)) {
98
+ return this._scoreData?.score;
99
+ } else if (this.clients.size < 1) {
100
+ return {
101
+ remarks: [ {
102
+ severity: 'none',
103
+ text: 'No clients',
104
+ } ],
105
+ score: 5.0,
106
+ timestamp: now,
107
+ };
108
+ }
109
+ const scores = [ ...this.clients.values() ].map((client) => client.score?.score ?? 5.0);
110
+
111
+ this._scoreData = {
112
+ score: {
113
+ remarks: [],
114
+ score: getMedian(scores),
115
+ timestamp: now,
116
+ },
117
+ calculated: now,
118
+ };
119
+
120
+ return this._scoreData.score;
121
+ }
122
+
87
123
  public get closed() {
88
124
  return this._closed;
89
125
  }
@@ -11,6 +11,7 @@ import { CallEventType } from './common/CallEventType';
11
11
  import { ObservedSfu } from './ObservedSfu';
12
12
  import { ClientIssue } from './monitors/CallSummary';
13
13
  import { CallEventReport } from '@observertc/report-schemas-js';
14
+ import { CalculatedScore } from './common/CalculatedScore';
14
15
 
15
16
  const logger = createLogger('ObservedClient');
16
17
 
@@ -59,6 +60,7 @@ export type ObservedClientEvents = {
59
60
  rejoined: [{
60
61
  lastJoined: number,
61
62
  }],
63
+ score: [CalculatedScore],
62
64
  };
63
65
 
64
66
  export declare interface ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> {
@@ -84,6 +86,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
84
86
  private _timeZoneOffsetInHours?: number;
85
87
  private _left?: number;
86
88
 
89
+ public score?: CalculatedScore;
87
90
  public usingTURN = false;
88
91
  public availableOutgoingBitrate = 0;
89
92
  public availableIncomingBitrate = 0;
@@ -114,6 +117,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
114
117
  public outboundVideoBitrate?: number;
115
118
  public inboundAudioBitrate?: number;
116
119
  public inboundVideoBitrate?: number;
120
+ public mediaConstraints: string[] = [];
117
121
 
118
122
  public readonly mediaDevices: string[] = [];
119
123
  public readonly codecs: string[] = [];
@@ -238,7 +242,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
238
242
  if (!this._left) {
239
243
  this._addClientLeftReport();
240
244
  }
241
-
242
245
  Array.from(this._peerConnections.values()).forEach((peerConnection) => peerConnection.close());
243
246
 
244
247
  this.emit('close');
@@ -302,8 +305,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
302
305
  logger.warn(`Error adding client issue: ${(err as Error)?.message}`);
303
306
  }
304
307
 
305
- this.issues.push(issue);
306
- this.emit('issue', issue);
308
+ this._addAndEmitIssue(issue);
307
309
  }
308
310
 
309
311
  public accept(sample: ClientSample): void {
@@ -452,6 +454,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
452
454
  );
453
455
 
454
456
  this.reports.addCallMetaReport(callMetaReport);
457
+ this.mediaConstraints.push(mediaConstraint);
455
458
  }
456
459
 
457
460
  for (const localSDP of sample.localSDPs ?? []) {
@@ -515,9 +518,8 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
515
518
  trackId: callEvent.mediaTrackId,
516
519
  attachments: callEvent.attachments ? JSON.parse(callEvent.attachments) : undefined,
517
520
  };
518
-
519
- this.issues.push(issue);
520
- this.emit('issue', issue);
521
+
522
+ this._addAndEmitIssue(issue);
521
523
  } catch (err) {
522
524
  logger.warn(`Error parsing client issue: ${(err as Error)?.message}`);
523
525
  }
@@ -619,7 +621,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
619
621
  type: CallMetaType.MEDIA_DEVICE,
620
622
  payload: mediaDevice,
621
623
  }, this.userId);
622
-
624
+
623
625
  this.reports.addCallMetaReport(callMetaReport);
624
626
  mediaDevice.label && this.mediaDevices.push(mediaDevice.label);
625
627
  }
@@ -634,7 +636,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
634
636
  type: CallMetaType.MEDIA_SOURCE,
635
637
  payload: mediaSource,
636
638
  }, this.userId);
637
-
639
+
638
640
  this.reports.addCallMetaReport(callMetaReport);
639
641
  }
640
642
 
@@ -670,10 +672,17 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
670
672
 
671
673
  inboundAudioTrack.update(track, sample.timestamp);
672
674
 
673
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundAudioTrack.get(track.sfuStreamId ?? '');
674
-
675
- if (remoteOutboundTrack && inboundAudioTrack.sfuStreamId && !remoteOutboundTrack.remoteInboundTracks.has(inboundAudioTrack.sfuStreamId)) {
676
- remoteOutboundTrack.connectInboundTrack(inboundAudioTrack);
675
+ if (!inboundAudioTrack.remoteOutboundTrack) {
676
+ const remoteOutboundTrack = this.call.sfuStreamIdToOutboundAudioTrack.get(inboundAudioTrack.sfuStreamId ?? '');
677
+
678
+ if (remoteOutboundTrack) {
679
+ inboundAudioTrack.remoteOutboundTrack = remoteOutboundTrack;
680
+
681
+ inboundAudioTrack.once('close', () => {
682
+ remoteOutboundTrack?.remoteInboundTracks.delete(inboundAudioTrack.trackId ?? '');
683
+ });
684
+ remoteOutboundTrack.remoteInboundTracks.set(inboundAudioTrack.trackId ?? '', inboundAudioTrack);
685
+ }
677
686
  }
678
687
  } catch (err) {
679
688
  logger.error(`Error creating inbound audio track: ${(err as Error)?.message}`);
@@ -705,11 +714,18 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
705
714
  });
706
715
 
707
716
  inboundVideoTrack.update(track, sample.timestamp);
708
-
709
- const remoteOutboundTrack = this.call.sfuStreamIdToOutboundVideoTrack.get(inboundVideoTrack.sfuStreamId ?? '');
710
-
711
- if (remoteOutboundTrack && inboundVideoTrack.sfuStreamId && !remoteOutboundTrack.remoteInboundTracks.has(inboundVideoTrack.sfuStreamId)) {
712
- remoteOutboundTrack.connectInboundTrack(inboundVideoTrack);
717
+
718
+ if (!inboundVideoTrack.remoteOutboundTrack) {
719
+ const remoteOutboundTrack = this.call.sfuStreamIdToOutboundVideoTrack.get(inboundVideoTrack.sfuStreamId ?? '');
720
+
721
+ if (remoteOutboundTrack) {
722
+ inboundVideoTrack.remoteOutboundTrack = remoteOutboundTrack;
723
+
724
+ inboundVideoTrack.once('close', () => {
725
+ remoteOutboundTrack?.remoteInboundTracks.delete(inboundVideoTrack.trackId ?? '');
726
+ });
727
+ remoteOutboundTrack.remoteInboundTracks.set(inboundVideoTrack.trackId ?? '', inboundVideoTrack);
728
+ }
713
729
  }
714
730
  } catch (err) {
715
731
  logger.error(`Error creating inbound video track: ${(err as Error)?.message}`);
@@ -734,6 +750,11 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
734
750
  });
735
751
 
736
752
  outboundAudioTrack.update(track, sample.timestamp);
753
+
754
+ if (outboundAudioTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundAudioTrack.has(outboundAudioTrack.sfuStreamId)) {
755
+ this.call.sfuStreamIdToOutboundAudioTrack.set(outboundAudioTrack.sfuStreamId, outboundAudioTrack);
756
+ }
757
+
737
758
  } catch (err) {
738
759
  logger.error(`Error creating outbound audio track: ${(err as Error)?.message}`);
739
760
  }
@@ -759,6 +780,10 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
759
780
 
760
781
  outboundVideoTrack.update(track, sample.timestamp);
761
782
 
783
+ if (outboundVideoTrack.sfuStreamId && !this.call.sfuStreamIdToOutboundVideoTrack.has(outboundVideoTrack.sfuStreamId)) {
784
+ this.call.sfuStreamIdToOutboundVideoTrack.set(outboundVideoTrack.sfuStreamId, outboundVideoTrack);
785
+ }
786
+
762
787
  } catch (err) {
763
788
  logger.error(`Error creating outbound video track: ${(err as Error)?.message}`);
764
789
  }
@@ -897,6 +922,9 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
897
922
  let sumRttInMs = 0;
898
923
  let anyPeerConnectionUsingTurn = false;
899
924
 
925
+ let minPcScore = Number.MAX_SAFE_INTEGER;
926
+ let maxPcScore = Number.MIN_SAFE_INTEGER;
927
+
900
928
  for (const peerConnection of this._peerConnections.values()) {
901
929
  if (peerConnection.closed) continue;
902
930
  peerConnection.updateMetrics();
@@ -916,8 +944,21 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
916
944
  sumRttInMs += peerConnection.avgRttInMs ?? 0;
917
945
 
918
946
  anyPeerConnectionUsingTurn ||= peerConnection.usingTURN;
947
+
948
+ minPcScore = Math.min(minPcScore, peerConnection.score?.score ?? Number.MAX_SAFE_INTEGER);
949
+ maxPcScore = Math.max(maxPcScore, peerConnection.score?.score ?? Number.MIN_SAFE_INTEGER);
919
950
  }
920
951
 
952
+ this.score = {
953
+ score: minPcScore,
954
+ remarks: [ {
955
+ severity: 'none',
956
+ text: `Min and max score of all peer connections: ${minPcScore}, ${maxPcScore}`,
957
+ } ],
958
+ timestamp: sample.timestamp,
959
+ };
960
+ this.emit('score', this.score);
961
+
921
962
  this.usingTURN = anyPeerConnectionUsingTurn === true;
922
963
 
923
964
  if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
@@ -951,6 +992,27 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
951
992
  });
952
993
  }
953
994
 
995
+ private _addAndEmitIssue(issue: ClientIssue) {
996
+ this.issues.push(issue);
997
+
998
+ if (issue.peerConnectionId) {
999
+ const peerConnection = this._peerConnections.get(issue.peerConnectionId);
1000
+
1001
+ if (peerConnection) {
1002
+
1003
+ if (issue.trackId) {
1004
+ const track = peerConnection.inboundAudioTracks.get(issue.trackId) ?? peerConnection.inboundVideoTracks.get(issue.trackId);
1005
+
1006
+ if (track) track.ωpendingIssuesForScores.push(issue);
1007
+ } else {
1008
+ peerConnection.ωpendingIssuesForScores.push(issue);
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ this.emit('issue', issue);
1014
+ }
1015
+
954
1016
  private _createPeerConnection(peerConnectionId: string, label?: string): ObservedPeerConnection {
955
1017
  const result = new ObservedPeerConnection({
956
1018
  peerConnectionId,