@observertc/observer-js 0.30.1-beta → 0.30.3-beta
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.
- package/lib/ObservedClient.d.ts +15 -6
- package/lib/ObservedClient.d.ts.map +1 -1
- package/lib/ObservedClient.js +146 -73
- package/lib/ObservedICE.d.ts +47 -0
- package/lib/ObservedICE.d.ts.map +1 -0
- package/lib/ObservedICE.js +105 -0
- package/lib/ObservedInboundTrack.d.ts +29 -1
- package/lib/ObservedInboundTrack.d.ts.map +1 -1
- package/lib/ObservedInboundTrack.js +78 -8
- package/lib/ObservedOutboundTrack.d.ts +20 -3
- package/lib/ObservedOutboundTrack.d.ts.map +1 -1
- package/lib/ObservedOutboundTrack.js +53 -9
- package/lib/ObservedPeerConnection.d.ts +47 -1
- package/lib/ObservedPeerConnection.d.ts.map +1 -1
- package/lib/ObservedPeerConnection.js +155 -0
- package/lib/common/CallEventType.d.ts +22 -0
- package/lib/common/CallEventType.d.ts.map +1 -0
- package/lib/common/CallEventType.js +26 -0
- package/lib/common/callEventReports.d.ts +0 -10
- package/lib/common/callEventReports.d.ts.map +1 -1
- package/lib/common/callEventReports.js +10 -21
- package/lib/models/Models.d.ts +3 -70
- package/lib/models/Models.d.ts.map +1 -1
- package/lib/models/Models.js +2 -72
- package/lib/models/samples_pb.js +1 -1
- package/package.json +1 -1
- package/src/ObservedClient.ts +146 -89
- package/src/ObservedICE.ts +176 -0
- package/src/ObservedInboundTrack.ts +97 -3
- package/src/ObservedOutboundTrack.ts +70 -9
- package/src/ObservedPeerConnection.ts +205 -1
- package/src/common/CallEventType.ts +23 -0
- package/src/common/callEventReports.ts +1 -11
- package/src/models/Models.ts +4 -113
- package/src/models/samples_pb.ts +1 -1
- package/lib/common/ReportsCollector.d.ts +0 -23
- package/lib/common/ReportsCollector.d.ts.map +0 -1
- package/lib/common/ReportsCollector.js +0 -26
- package/src/common/ReportsCollector.ts +0 -71
package/lib/models/Models.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// @generated by protoc-gen-es v1.
|
|
2
|
+
// @generated by protoc-gen-es v1.7.2 with parameter "target=ts"
|
|
3
3
|
// @generated from file models.proto (package org.observertc.observer.models, syntax proto2)
|
|
4
4
|
/* eslint-disable */
|
|
5
5
|
// @ts-nocheck
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.SfuSctpChannel = exports.SfuOutboundRtpPad = exports.SfuInboundRtpPad = exports.SfuTransport = exports.Sfu = exports.OutboundTrack = exports.InboundTrack = exports.InboundTrackStats = exports.InboundTrackVideoStats = exports.PeerConnection = exports.Client = exports.
|
|
7
|
+
exports.SfuSctpChannel = exports.SfuOutboundRtpPad = exports.SfuInboundRtpPad = exports.SfuTransport = exports.Sfu = exports.OutboundTrack = exports.InboundTrack = exports.InboundTrackStats = exports.InboundTrackVideoStats = exports.PeerConnection = exports.Client = exports.Call = void 0;
|
|
8
8
|
const protobuf_1 = require("@bufbuild/protobuf");
|
|
9
9
|
const samples_pb_1 = require("./samples_pb");
|
|
10
10
|
/**
|
|
@@ -42,34 +42,6 @@ Call.fields = protobuf_1.proto2.util.newFieldList(() => [
|
|
|
42
42
|
{ no: 4, name: "started", kind: "scalar", T: 4 /* ScalarType.UINT64 */ },
|
|
43
43
|
{ no: 5, name: "clientIds", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
|
|
44
44
|
]);
|
|
45
|
-
/**
|
|
46
|
-
* @generated from message org.observertc.observer.models.ClientCoordinate
|
|
47
|
-
*/
|
|
48
|
-
class ClientCoordinate extends protobuf_1.Message {
|
|
49
|
-
constructor(data) {
|
|
50
|
-
super();
|
|
51
|
-
protobuf_1.proto2.util.initPartial(data, this);
|
|
52
|
-
}
|
|
53
|
-
static fromBinary(bytes, options) {
|
|
54
|
-
return new ClientCoordinate().fromBinary(bytes, options);
|
|
55
|
-
}
|
|
56
|
-
static fromJson(jsonValue, options) {
|
|
57
|
-
return new ClientCoordinate().fromJson(jsonValue, options);
|
|
58
|
-
}
|
|
59
|
-
static fromJsonString(jsonString, options) {
|
|
60
|
-
return new ClientCoordinate().fromJsonString(jsonString, options);
|
|
61
|
-
}
|
|
62
|
-
static equals(a, b) {
|
|
63
|
-
return protobuf_1.proto2.util.equals(ClientCoordinate, a, b);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
exports.ClientCoordinate = ClientCoordinate;
|
|
67
|
-
ClientCoordinate.runtime = protobuf_1.proto2;
|
|
68
|
-
ClientCoordinate.typeName = "org.observertc.observer.models.ClientCoordinate";
|
|
69
|
-
ClientCoordinate.fields = protobuf_1.proto2.util.newFieldList(() => [
|
|
70
|
-
{ no: 1, name: "latitude", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
|
|
71
|
-
{ no: 2, name: "longitude", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
|
|
72
|
-
]);
|
|
73
45
|
/**
|
|
74
46
|
* @generated from message org.observertc.observer.models.Client
|
|
75
47
|
*/
|
|
@@ -114,7 +86,6 @@ Client.fields = protobuf_1.proto2.util.newFieldList(() => [
|
|
|
114
86
|
{ no: 13, name: "operationSystem", kind: "message", T: samples_pb_1.Samples_ClientSample_OperationSystem, opt: true },
|
|
115
87
|
{ no: 14, name: "platform", kind: "message", T: samples_pb_1.Samples_ClientSample_Platform, opt: true },
|
|
116
88
|
{ no: 15, name: "engine", kind: "message", T: samples_pb_1.Samples_ClientSample_Engine, opt: true },
|
|
117
|
-
{ no: 16, name: "coordinates", kind: "message", T: ClientCoordinate, opt: true },
|
|
118
89
|
]);
|
|
119
90
|
/**
|
|
120
91
|
* @generated from message org.observertc.observer.models.PeerConnection
|
|
@@ -130,22 +101,6 @@ class PeerConnection extends protobuf_1.Message {
|
|
|
130
101
|
* @generated from field: repeated string outboundTrackIds = 12;
|
|
131
102
|
*/
|
|
132
103
|
this.outboundTrackIds = [];
|
|
133
|
-
/**
|
|
134
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.IceLocalCandidate icelocalCandidates = 13;
|
|
135
|
-
*/
|
|
136
|
-
this.icelocalCandidates = [];
|
|
137
|
-
/**
|
|
138
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.IceRemoteCandidate iceRemoteCandidates = 14;
|
|
139
|
-
*/
|
|
140
|
-
this.iceRemoteCandidates = [];
|
|
141
|
-
/**
|
|
142
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.IceCandidatePair iceCandidatePairs = 15;
|
|
143
|
-
*/
|
|
144
|
-
this.iceCandidatePairs = [];
|
|
145
|
-
/**
|
|
146
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.PeerConnectionTransport transports = 16;
|
|
147
|
-
*/
|
|
148
|
-
this.transports = [];
|
|
149
104
|
protobuf_1.proto2.util.initPartial(data, this);
|
|
150
105
|
}
|
|
151
106
|
static fromBinary(bytes, options) {
|
|
@@ -177,10 +132,6 @@ PeerConnection.fields = protobuf_1.proto2.util.newFieldList(() => [
|
|
|
177
132
|
{ no: 10, name: "label", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
|
|
178
133
|
{ no: 11, name: "inboundTrackIds", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
|
|
179
134
|
{ no: 12, name: "outboundTrackIds", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
|
|
180
|
-
{ no: 13, name: "icelocalCandidates", kind: "message", T: samples_pb_1.Samples_ClientSample_IceLocalCandidate, repeated: true },
|
|
181
|
-
{ no: 14, name: "iceRemoteCandidates", kind: "message", T: samples_pb_1.Samples_ClientSample_IceRemoteCandidate, repeated: true },
|
|
182
|
-
{ no: 15, name: "iceCandidatePairs", kind: "message", T: samples_pb_1.Samples_ClientSample_IceCandidatePair, repeated: true },
|
|
183
|
-
{ no: 16, name: "transports", kind: "message", T: samples_pb_1.Samples_ClientSample_PeerConnectionTransport, repeated: true },
|
|
184
135
|
{ no: 17, name: "touched", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true },
|
|
185
136
|
]);
|
|
186
137
|
/**
|
|
@@ -210,7 +161,6 @@ InboundTrackVideoStats.typeName = "org.observertc.observer.models.InboundTrackVi
|
|
|
210
161
|
InboundTrackVideoStats.fields = protobuf_1.proto2.util.newFieldList(() => [
|
|
211
162
|
{ no: 1, name: "ssrc", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
|
|
212
163
|
{ no: 2, name: "fps", kind: "scalar", T: 2 /* ScalarType.FLOAT */, opt: true },
|
|
213
|
-
{ no: 4, name: "sample", kind: "message", T: samples_pb_1.Samples_ClientSample_InboundVideoTrack, opt: true },
|
|
214
164
|
]);
|
|
215
165
|
/**
|
|
216
166
|
* @generated from message org.observertc.observer.models.InboundTrackStats
|
|
@@ -251,14 +201,6 @@ class InboundTrack extends protobuf_1.Message {
|
|
|
251
201
|
* @generated from field: repeated int64 ssrc = 14;
|
|
252
202
|
*/
|
|
253
203
|
this.ssrc = [];
|
|
254
|
-
/**
|
|
255
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.InboundAudioTrack audioStats = 15;
|
|
256
|
-
*/
|
|
257
|
-
this.audioStats = [];
|
|
258
|
-
/**
|
|
259
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.InboundVideoTrack videoStats = 16;
|
|
260
|
-
*/
|
|
261
|
-
this.videoStats = [];
|
|
262
204
|
protobuf_1.proto2.util.initPartial(data, this);
|
|
263
205
|
}
|
|
264
206
|
static fromBinary(bytes, options) {
|
|
@@ -292,8 +234,6 @@ InboundTrack.fields = protobuf_1.proto2.util.newFieldList(() => [
|
|
|
292
234
|
{ no: 12, name: "sfuSinkId", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
|
|
293
235
|
{ no: 13, name: "sfuStreamId", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
|
|
294
236
|
{ no: 14, name: "ssrc", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true },
|
|
295
|
-
{ no: 15, name: "audioStats", kind: "message", T: samples_pb_1.Samples_ClientSample_InboundAudioTrack, repeated: true },
|
|
296
|
-
{ no: 16, name: "videoStats", kind: "message", T: samples_pb_1.Samples_ClientSample_InboundVideoTrack, repeated: true },
|
|
297
237
|
{ no: 17, name: "touched", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true },
|
|
298
238
|
]);
|
|
299
239
|
/**
|
|
@@ -306,14 +246,6 @@ class OutboundTrack extends protobuf_1.Message {
|
|
|
306
246
|
* @generated from field: repeated int64 ssrc = 13;
|
|
307
247
|
*/
|
|
308
248
|
this.ssrc = [];
|
|
309
|
-
/**
|
|
310
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.OutboundAudioTrack audioStats = 14;
|
|
311
|
-
*/
|
|
312
|
-
this.audioStats = [];
|
|
313
|
-
/**
|
|
314
|
-
* @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.OutboundVideoTrack videoStats = 15;
|
|
315
|
-
*/
|
|
316
|
-
this.videoStats = [];
|
|
317
249
|
protobuf_1.proto2.util.initPartial(data, this);
|
|
318
250
|
}
|
|
319
251
|
static fromBinary(bytes, options) {
|
|
@@ -346,8 +278,6 @@ OutboundTrack.fields = protobuf_1.proto2.util.newFieldList(() => [
|
|
|
346
278
|
{ no: 11, name: "marker", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
|
|
347
279
|
{ no: 12, name: "sfuStreamId", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
|
|
348
280
|
{ no: 13, name: "ssrc", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true },
|
|
349
|
-
{ no: 14, name: "audioStats", kind: "message", T: samples_pb_1.Samples_ClientSample_OutboundAudioTrack, repeated: true },
|
|
350
|
-
{ no: 15, name: "videoStats", kind: "message", T: samples_pb_1.Samples_ClientSample_OutboundVideoTrack, repeated: true },
|
|
351
281
|
{ no: 16, name: "touched", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true },
|
|
352
282
|
]);
|
|
353
283
|
/**
|
package/lib/models/samples_pb.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// @generated by protoc-gen-es v1.
|
|
2
|
+
// @generated by protoc-gen-es v1.7.2 with parameter "target=ts"
|
|
3
3
|
// @generated from file samples.proto (package org.observertc.schemas.protobuf, syntax proto2)
|
|
4
4
|
/* eslint-disable */
|
|
5
5
|
// @ts-nocheck
|
package/package.json
CHANGED
package/src/ObservedClient.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientSample
|
|
1
|
+
import { ClientSample } from '@observertc/sample-schemas-js';
|
|
2
2
|
import { ObservedCall } from './ObservedCall';
|
|
3
3
|
import * as Models from './models/Models';
|
|
4
4
|
import { StorageProvider } from './storages/StorageProvider';
|
|
@@ -9,9 +9,9 @@ import { createLogger } from './common/logger';
|
|
|
9
9
|
import { CallMetaType, createCallMetaReport } from './common/callMetaReports';
|
|
10
10
|
// eslint-disable-next-line camelcase
|
|
11
11
|
import { Samples_ClientSample_Browser, Samples_ClientSample_Engine, Samples_ClientSample_OperationSystem, Samples_ClientSample_Platform } from './models/samples_pb';
|
|
12
|
-
import { IceCandidatePairReport } from '@observertc/report-schemas-js';
|
|
13
12
|
import { isValidUuid } from './common/utils';
|
|
14
13
|
import { createClientJoinedEventReport, createClientLeftEventReport } from './common/callEventReports';
|
|
14
|
+
import { CallEventType } from './common/CallEventType';
|
|
15
15
|
|
|
16
16
|
const logger = createLogger('ObservedClient');
|
|
17
17
|
|
|
@@ -33,7 +33,6 @@ export type ObservedClientEvents = {
|
|
|
33
33
|
update: [],
|
|
34
34
|
close: [],
|
|
35
35
|
'processing-sample-overflow': [],
|
|
36
|
-
'candidate-pair-change': [],
|
|
37
36
|
newpeerconnection: [ObservedPeerConnection],
|
|
38
37
|
};
|
|
39
38
|
|
|
@@ -89,6 +88,8 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
89
88
|
return result;
|
|
90
89
|
}
|
|
91
90
|
|
|
91
|
+
public created = Date.now();
|
|
92
|
+
|
|
92
93
|
private readonly _peerConnections = new Map<string, ObservedPeerConnection>();
|
|
93
94
|
private readonly _execute = createSingleExecutor();
|
|
94
95
|
|
|
@@ -97,15 +98,28 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
97
98
|
private _updated = Date.now();
|
|
98
99
|
private _acceptedSample = 0;
|
|
99
100
|
private _processingSample = 0;
|
|
100
|
-
private _selectedLocalIceCandidate?: IceLocalCandidate;
|
|
101
|
-
private _selectedRemoteIceCandidate?: IceLocalCandidate;
|
|
102
101
|
private _timeZoneOffsetInHours?: number;
|
|
103
102
|
private _leaveReported = false;
|
|
104
103
|
|
|
104
|
+
public totalInboundPacketsLost = 0;
|
|
105
|
+
public totalInboundPacketsReceived = 0;
|
|
106
|
+
public totalOutboundPacketsSent = 0;
|
|
107
|
+
public totalDataChannelBytesSent = 0;
|
|
108
|
+
public totalDataChannelBytesReceived = 0;
|
|
109
|
+
public totalSentBytes = 0;
|
|
110
|
+
public totalReceivedBytes = 0;
|
|
111
|
+
|
|
112
|
+
public avgRttInS?: number;
|
|
113
|
+
public sendingBitrate?: number;
|
|
114
|
+
public receivingBitrate?: number;
|
|
115
|
+
|
|
105
116
|
public readonly mediaDevices: string[] = [];
|
|
106
117
|
public readonly codecs: string[] = [];
|
|
107
118
|
public readonly userMediaErrors: string[] = [];
|
|
108
119
|
|
|
120
|
+
public readonly ωpendingCreatedTracksTimestamp = new Map<string, number>();
|
|
121
|
+
public readonly ωpendingCreatedPeerConnectionTimestamp = new Map<string, number>();
|
|
122
|
+
|
|
109
123
|
private constructor(
|
|
110
124
|
private readonly _model: Models.Client,
|
|
111
125
|
public readonly call: ObservedCall,
|
|
@@ -153,14 +167,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
153
167
|
return this._model.userId;
|
|
154
168
|
}
|
|
155
169
|
|
|
156
|
-
public get selectedLocalIceCandidate() {
|
|
157
|
-
return this._selectedLocalIceCandidate;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public get selectedRemoteIceCandidate() {
|
|
161
|
-
return this._selectedRemoteIceCandidate;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
170
|
public get timeZoneOffsetInHours() {
|
|
165
171
|
return this._timeZoneOffsetInHours;
|
|
166
172
|
}
|
|
@@ -193,6 +199,10 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
193
199
|
return this._updated;
|
|
194
200
|
}
|
|
195
201
|
|
|
202
|
+
public get uptimeInMs() {
|
|
203
|
+
return this._updated - this.created;
|
|
204
|
+
}
|
|
205
|
+
|
|
196
206
|
public get closed() {
|
|
197
207
|
return this._closed;
|
|
198
208
|
}
|
|
@@ -225,6 +235,11 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
return this._execute(async () => {
|
|
238
|
+
for (const peerConnection of this._peerConnections.values()) {
|
|
239
|
+
if (peerConnection.closed) continue;
|
|
240
|
+
peerConnection.resetMetrics();
|
|
241
|
+
}
|
|
242
|
+
|
|
228
243
|
let executeSave = false;
|
|
229
244
|
|
|
230
245
|
if (this.userId !== sample.userId) {
|
|
@@ -379,9 +394,47 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
379
394
|
});
|
|
380
395
|
}
|
|
381
396
|
|
|
382
|
-
for (const { timestamp
|
|
383
|
-
|
|
397
|
+
for (const { timestamp, ...callEvent } of sample.customCallEvents ?? []) {
|
|
398
|
+
const peerConnection = this._peerConnections.get(callEvent.peerConnectionId ?? '');
|
|
384
399
|
|
|
400
|
+
switch (callEvent.name) {
|
|
401
|
+
case CallEventType.CLIENT_JOINED:
|
|
402
|
+
this.created = timestamp ?? this.created;
|
|
403
|
+
break;
|
|
404
|
+
case CallEventType.CLIENT_LEFT:
|
|
405
|
+
this._leaveReported = true;
|
|
406
|
+
break;
|
|
407
|
+
case CallEventType.ICE_CONNECTION_STATE_CHANGED:
|
|
408
|
+
peerConnection && callEvent.value && peerConnection.emit('iceconnectionstatechange', callEvent.value);
|
|
409
|
+
break;
|
|
410
|
+
case CallEventType.ICE_GATHERING_STATE_CHANGED:
|
|
411
|
+
peerConnection && callEvent.value && peerConnection.emit('icegatheringstatechange', callEvent.value);
|
|
412
|
+
break;
|
|
413
|
+
case CallEventType.PEER_CONNECTION_STATE_CHANGED:
|
|
414
|
+
peerConnection && callEvent.value && peerConnection.emit('connectionstatechange', callEvent.value);
|
|
415
|
+
break;
|
|
416
|
+
case CallEventType.PEER_CONNECTION_OPENED: {
|
|
417
|
+
if (!callEvent.peerConnectionId) break;
|
|
418
|
+
if (peerConnection) {
|
|
419
|
+
peerConnection.created = timestamp ?? peerConnection.created;
|
|
420
|
+
} else {
|
|
421
|
+
this.ωpendingCreatedPeerConnectionTimestamp.set(callEvent.peerConnectionId, timestamp ?? Date.now());
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case CallEventType.MEDIA_TRACK_ADDED: {
|
|
426
|
+
if (!callEvent.mediaTrackId) break;
|
|
427
|
+
const track = peerConnection?.getTrack(callEvent.mediaTrackId);
|
|
428
|
+
|
|
429
|
+
if (track) {
|
|
430
|
+
track.created = timestamp ?? track.created;
|
|
431
|
+
} else {
|
|
432
|
+
this.ωpendingCreatedTracksTimestamp.set(callEvent.mediaTrackId, timestamp ?? Date.now());
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
}
|
|
437
|
+
|
|
385
438
|
this.reports.addCallEventReport({
|
|
386
439
|
serviceId: this.serviceId,
|
|
387
440
|
mediaUnitId: this.mediaUnitId,
|
|
@@ -389,7 +442,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
389
442
|
callId: this.callId,
|
|
390
443
|
clientId: this.clientId,
|
|
391
444
|
userId: this.userId,
|
|
392
|
-
timestamp,
|
|
445
|
+
timestamp: timestamp ?? Date.now(),
|
|
393
446
|
...callEvent,
|
|
394
447
|
});
|
|
395
448
|
}
|
|
@@ -480,76 +533,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
480
533
|
|
|
481
534
|
this.reports.addCallMetaReport(callMetaReport);
|
|
482
535
|
}
|
|
483
|
-
let selectedLocalCandidateId: string | undefined;
|
|
484
|
-
let selectedRemoteCandidateId: string | undefined;
|
|
485
|
-
|
|
486
|
-
for (const candidatePair of sample.iceCandidatePairs ?? []) {
|
|
487
|
-
if (candidatePair.nominated) {
|
|
488
|
-
if (!selectedLocalCandidateId) selectedLocalCandidateId = candidatePair.localCandidateId;
|
|
489
|
-
if (!selectedRemoteCandidateId) selectedRemoteCandidateId = candidatePair.remoteCandidateId;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const report: IceCandidatePairReport = {
|
|
493
|
-
serviceId: this.serviceId,
|
|
494
|
-
mediaUnitId: this.mediaUnitId,
|
|
495
|
-
roomId: this.roomId,
|
|
496
|
-
callId: this.callId,
|
|
497
|
-
clientId: this.clientId,
|
|
498
|
-
userId: this.userId,
|
|
499
|
-
timestamp: sample.timestamp,
|
|
500
|
-
...candidatePair,
|
|
501
|
-
sampleSeq: -1, // deprecated
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
this.reports.addIceCandidatePairReport(report);
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
let changeIceCandidatePair = false;
|
|
509
|
-
|
|
510
|
-
for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
|
|
511
|
-
if (
|
|
512
|
-
iceLocalCandidate.id && selectedLocalCandidateId &&
|
|
513
|
-
iceLocalCandidate.id === selectedLocalCandidateId &&
|
|
514
|
-
this._selectedLocalIceCandidate?.id !== iceLocalCandidate.id
|
|
515
|
-
) {
|
|
516
|
-
this._selectedLocalIceCandidate = iceLocalCandidate;
|
|
517
|
-
changeIceCandidatePair = true;
|
|
518
|
-
}
|
|
519
|
-
const callMetaReport = createCallMetaReport(
|
|
520
|
-
this.serviceId,
|
|
521
|
-
this.mediaUnitId,
|
|
522
|
-
this.roomId,
|
|
523
|
-
this.callId,
|
|
524
|
-
this.clientId, {
|
|
525
|
-
type: CallMetaType.ICE_LOCAL_CANDIDATE,
|
|
526
|
-
payload: iceLocalCandidate,
|
|
527
|
-
}, this.userId);
|
|
528
|
-
|
|
529
|
-
this.reports.addCallMetaReport(callMetaReport);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
|
|
533
|
-
if (
|
|
534
|
-
iceRemoteCandidate.id && selectedRemoteCandidateId &&
|
|
535
|
-
iceRemoteCandidate.id === selectedRemoteCandidateId &&
|
|
536
|
-
this._selectedRemoteIceCandidate?.id !== iceRemoteCandidate.id
|
|
537
|
-
) {
|
|
538
|
-
this._selectedRemoteIceCandidate = iceRemoteCandidate;
|
|
539
|
-
changeIceCandidatePair = true;
|
|
540
|
-
}
|
|
541
|
-
const callMetaReport = createCallMetaReport(
|
|
542
|
-
this.serviceId,
|
|
543
|
-
this.mediaUnitId,
|
|
544
|
-
this.roomId,
|
|
545
|
-
this.callId,
|
|
546
|
-
this.clientId, {
|
|
547
|
-
type: CallMetaType.ICE_REMOTE_CANDIDATE,
|
|
548
|
-
payload: iceRemoteCandidate,
|
|
549
|
-
}, this.userId);
|
|
550
|
-
|
|
551
|
-
this.reports.addCallMetaReport(callMetaReport);
|
|
552
|
-
}
|
|
553
536
|
|
|
554
537
|
for (const transport of sample.pcTransports ?? []) {
|
|
555
538
|
try {
|
|
@@ -676,6 +659,48 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
676
659
|
}
|
|
677
660
|
}
|
|
678
661
|
|
|
662
|
+
for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
|
|
663
|
+
if (!iceLocalCandidate.peerConnectionId) {
|
|
664
|
+
logger.warn(`Local ice candidate without peerConnectionId: ${JSON.stringify(iceLocalCandidate)}`);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const peerConnection = this._peerConnections.get(iceLocalCandidate.peerConnectionId);
|
|
668
|
+
|
|
669
|
+
if (!peerConnection) {
|
|
670
|
+
logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
peerConnection.ICE.addLocalCandidate(iceLocalCandidate);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
|
|
678
|
+
if (!iceRemoteCandidate.peerConnectionId) {
|
|
679
|
+
logger.warn(`Remote ice candidate without peerConnectionId: ${JSON.stringify(iceRemoteCandidate)}`);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const peerConnection = this._peerConnections.get(iceRemoteCandidate.peerConnectionId);
|
|
683
|
+
|
|
684
|
+
if (!peerConnection) {
|
|
685
|
+
logger.debug(`Peer connection ${iceRemoteCandidate.peerConnectionId} not found for ice remote candidate ${iceRemoteCandidate.id}`);
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
for (const candidatePair of sample.iceCandidatePairs ?? []) {
|
|
693
|
+
const peerConnection = this._peerConnections.get(candidatePair.peerConnectionId);
|
|
694
|
+
|
|
695
|
+
if (!peerConnection) {
|
|
696
|
+
logger.debug(`Peer connection ${candidatePair.peerConnectionId} not found for ice candidate pair ${candidatePair.localCandidateId}, ${candidatePair.remoteCandidateId}`);
|
|
697
|
+
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
peerConnection.ICE.update(candidatePair, sample.timestamp);
|
|
702
|
+
}
|
|
703
|
+
|
|
679
704
|
// close entries that are idle for too long
|
|
680
705
|
if (this.call.observer.config.maxEntryIdleTimeInMs && this._updated < sample.timestamp) {
|
|
681
706
|
|
|
@@ -706,10 +731,35 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
706
731
|
}
|
|
707
732
|
}
|
|
708
733
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
734
|
+
|
|
735
|
+
// update metrics
|
|
736
|
+
this.totalSentBytes = 0;
|
|
737
|
+
this.totalReceivedBytes = 0;
|
|
738
|
+
this.totalOutboundPacketsSent = 0;
|
|
739
|
+
this.totalInboundPacketsReceived = 0;
|
|
740
|
+
this.totalInboundPacketsLost = 0;
|
|
741
|
+
this.totalDataChannelBytesSent = 0;
|
|
742
|
+
this.totalDataChannelBytesReceived = 0;
|
|
743
|
+
this.sendingBitrate = 0;
|
|
744
|
+
this.receivingBitrate = 0;
|
|
745
|
+
let sumRttInS = 0;
|
|
746
|
+
|
|
747
|
+
for (const peerConnection of this._peerConnections.values()) {
|
|
748
|
+
if (peerConnection.closed) continue;
|
|
749
|
+
peerConnection.updateMetrics();
|
|
750
|
+
this.totalSentBytes += peerConnection.totalSentAudioBytes + peerConnection.totalSentVideoBytes;
|
|
751
|
+
this.totalReceivedBytes += peerConnection.totalReceivedAudioBytes + peerConnection.totalReceivedVideoBytes;
|
|
752
|
+
this.totalOutboundPacketsSent += peerConnection.totalOutboundPacketsSent;
|
|
753
|
+
this.totalInboundPacketsReceived += peerConnection.totalInboundPacketsReceived;
|
|
754
|
+
this.totalInboundPacketsLost += peerConnection.totalInboundPacketsLost;
|
|
755
|
+
this.totalDataChannelBytesSent += peerConnection.totalDataChannelBytesSent;
|
|
756
|
+
this.totalDataChannelBytesReceived += peerConnection.totalDataChannelBytesReceived;
|
|
757
|
+
this.sendingBitrate += peerConnection.sendingAudioBitrate + peerConnection.sendingVideoBitrate;
|
|
758
|
+
this.receivingBitrate += peerConnection.receivingAudioBitrate + peerConnection.receivingVideoBitrate;
|
|
759
|
+
sumRttInS += peerConnection.avgRttInS ?? 0;
|
|
712
760
|
}
|
|
761
|
+
|
|
762
|
+
this.avgRttInS = this._peerConnections.size ? sumRttInS / this._peerConnections.size : -1;
|
|
713
763
|
|
|
714
764
|
if (executeSave) await this._save();
|
|
715
765
|
|
|
@@ -725,6 +775,13 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
725
775
|
peerConnectionId,
|
|
726
776
|
}, this, this._storageProvider);
|
|
727
777
|
|
|
778
|
+
const pendingTracksTimestamp = this.ωpendingCreatedTracksTimestamp.get(peerConnectionId);
|
|
779
|
+
|
|
780
|
+
if (pendingTracksTimestamp) {
|
|
781
|
+
result.created = pendingTracksTimestamp;
|
|
782
|
+
this.ωpendingCreatedTracksTimestamp.delete(peerConnectionId);
|
|
783
|
+
}
|
|
784
|
+
|
|
728
785
|
result.once('close', () => {
|
|
729
786
|
this._peerConnections.delete(peerConnectionId);
|
|
730
787
|
this._model.peerConnectionIds = this._model.peerConnectionIds.filter((id) => id !== peerConnectionId);
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { IceCandidatePair, IceLocalCandidate, IceRemoteCandidate } from '@observertc/sample-schemas-js';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
4
|
+
import { IceCandidatePairReport } from '@observertc/report-schemas-js';
|
|
5
|
+
import { CallMetaType, createCallMetaReport } from './common/callMetaReports';
|
|
6
|
+
|
|
7
|
+
export type ObservedICEEvents = {
|
|
8
|
+
update: [],
|
|
9
|
+
'new-local-candidate': [IceLocalCandidate],
|
|
10
|
+
'new-remote-candidate': [IceRemoteCandidate],
|
|
11
|
+
'new-selected-candidate-pair': [{
|
|
12
|
+
localCandidate: IceLocalCandidate,
|
|
13
|
+
remoteCandidate: IceRemoteCandidate,
|
|
14
|
+
}],
|
|
15
|
+
close: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export declare interface ObservedICE {
|
|
19
|
+
on<U extends keyof ObservedICEEvents>(event: U, listener: (...args: ObservedICEEvents[U]) => void): this;
|
|
20
|
+
off<U extends keyof ObservedICEEvents>(event: U, listener: (...args: ObservedICEEvents[U]) => void): this;
|
|
21
|
+
once<U extends keyof ObservedICEEvents>(event: U, listener: (...args: ObservedICEEvents[U]) => void): this;
|
|
22
|
+
emit<U extends keyof ObservedICEEvents>(event: U, ...args: ObservedICEEvents[U]): boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ObservedICE extends EventEmitter {
|
|
26
|
+
public static create(peerConnection: ObservedPeerConnection,) {
|
|
27
|
+
return new ObservedICE(peerConnection);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private readonly _localCandidates = new Map<string, IceLocalCandidate>();
|
|
31
|
+
private readonly _remoteCandidates = new Map<string, IceLocalCandidate>();
|
|
32
|
+
|
|
33
|
+
private _selectedLocalCandidateId?: string;
|
|
34
|
+
private _selectedRemoteCandidateId?: string;
|
|
35
|
+
private _updated = Date.now();
|
|
36
|
+
private _stats?: IceCandidatePair;
|
|
37
|
+
private _closed = false;
|
|
38
|
+
|
|
39
|
+
private constructor(
|
|
40
|
+
public readonly peerConnection: ObservedPeerConnection,
|
|
41
|
+
) {
|
|
42
|
+
super();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public get reports() {
|
|
46
|
+
return this.peerConnection.reports;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public get localCandidates(): ReadonlyMap<string, IceLocalCandidate> {
|
|
50
|
+
return this._localCandidates;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public get remoteCandidates(): ReadonlyMap<string, IceRemoteCandidate> {
|
|
54
|
+
return this._remoteCandidates;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public get selectedLocalCandidate() {
|
|
58
|
+
return this._localCandidates.get(this._selectedLocalCandidateId ?? '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public get selectedRemoteCandidate() {
|
|
62
|
+
return this._remoteCandidates.get(this._selectedRemoteCandidateId ?? '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public get stats() {
|
|
66
|
+
return this._stats;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public get updated() {
|
|
70
|
+
return this._updated;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public get closed() {
|
|
74
|
+
return this._closed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public close() {
|
|
78
|
+
if (this._closed) return;
|
|
79
|
+
this._closed = true;
|
|
80
|
+
this.emit('close');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public addLocalCandidate(candidate: IceLocalCandidate) {
|
|
84
|
+
if (!candidate.id) return;
|
|
85
|
+
|
|
86
|
+
const newCandidate = !this._localCandidates.has(candidate.id);
|
|
87
|
+
|
|
88
|
+
this._localCandidates.set(candidate.id, candidate);
|
|
89
|
+
|
|
90
|
+
if (newCandidate) {
|
|
91
|
+
const callMetaReport = createCallMetaReport(
|
|
92
|
+
this.peerConnection.client.serviceId,
|
|
93
|
+
this.peerConnection.client.mediaUnitId,
|
|
94
|
+
this.peerConnection.client.roomId,
|
|
95
|
+
this.peerConnection.client.callId,
|
|
96
|
+
this.peerConnection.client.clientId, {
|
|
97
|
+
type: CallMetaType.ICE_LOCAL_CANDIDATE,
|
|
98
|
+
payload: candidate,
|
|
99
|
+
}, this.peerConnection.client.userId);
|
|
100
|
+
|
|
101
|
+
this.reports.addCallMetaReport(callMetaReport);
|
|
102
|
+
|
|
103
|
+
this.emit('new-local-candidate', candidate);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public addRemoteCandidate(candidate: IceRemoteCandidate) {
|
|
108
|
+
if (!candidate.id) return;
|
|
109
|
+
|
|
110
|
+
const newCandidate = !this._remoteCandidates.has(candidate.id);
|
|
111
|
+
|
|
112
|
+
this._remoteCandidates.set(candidate.id, candidate);
|
|
113
|
+
|
|
114
|
+
if (newCandidate) {
|
|
115
|
+
const callMetaReport = createCallMetaReport(
|
|
116
|
+
this.peerConnection.client.serviceId,
|
|
117
|
+
this.peerConnection.client.mediaUnitId,
|
|
118
|
+
this.peerConnection.client.roomId,
|
|
119
|
+
this.peerConnection.client.callId,
|
|
120
|
+
this.peerConnection.client.clientId, {
|
|
121
|
+
type: CallMetaType.ICE_REMOTE_CANDIDATE,
|
|
122
|
+
payload: candidate,
|
|
123
|
+
}, this.peerConnection.client.userId);
|
|
124
|
+
|
|
125
|
+
this.reports.addCallMetaReport(callMetaReport);
|
|
126
|
+
|
|
127
|
+
this.emit('new-remote-candidate', candidate);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public update(candidatePair: IceCandidatePair, timestamp: number) {
|
|
132
|
+
|
|
133
|
+
const report: IceCandidatePairReport = {
|
|
134
|
+
serviceId: this.peerConnection.client.serviceId,
|
|
135
|
+
mediaUnitId: this.peerConnection.client.mediaUnitId,
|
|
136
|
+
roomId: this.peerConnection.client.roomId,
|
|
137
|
+
callId: this.peerConnection.client.callId,
|
|
138
|
+
clientId: this.peerConnection.client.clientId,
|
|
139
|
+
userId: this.peerConnection.client.userId,
|
|
140
|
+
timestamp,
|
|
141
|
+
...candidatePair,
|
|
142
|
+
sampleSeq: -1, // deprecated
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
this.reports.addIceCandidatePairReport(report);
|
|
146
|
+
|
|
147
|
+
if (!candidatePair.nominated) return;
|
|
148
|
+
|
|
149
|
+
this._stats = candidatePair;
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
candidatePair.localCandidateId &&
|
|
153
|
+
candidatePair.localCandidateId !== this._selectedLocalCandidateId &&
|
|
154
|
+
this._localCandidates.has(candidatePair.localCandidateId) &&
|
|
155
|
+
candidatePair.remoteCandidateId &&
|
|
156
|
+
candidatePair.remoteCandidateId !== this._selectedRemoteCandidateId &&
|
|
157
|
+
this._remoteCandidates.has(candidatePair.remoteCandidateId)
|
|
158
|
+
) {
|
|
159
|
+
this._selectedLocalCandidateId = candidatePair.localCandidateId;
|
|
160
|
+
this._selectedRemoteCandidateId = candidatePair.remoteCandidateId;
|
|
161
|
+
|
|
162
|
+
const localCandidate = this._localCandidates.get(candidatePair.localCandidateId);
|
|
163
|
+
const remoteCandidate = this._remoteCandidates.get(candidatePair.remoteCandidateId);
|
|
164
|
+
|
|
165
|
+
if (localCandidate && remoteCandidate) {
|
|
166
|
+
this.emit('new-selected-candidate-pair', {
|
|
167
|
+
localCandidate,
|
|
168
|
+
remoteCandidate,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this._updated = timestamp;
|
|
174
|
+
this.emit('update');
|
|
175
|
+
}
|
|
176
|
+
}
|