@observertc/observer-js 0.30.1-beta → 0.30.2-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 +28 -6
- package/lib/ObservedClient.d.ts.map +1 -1
- package/lib/ObservedClient.js +84 -75
- 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 +16 -0
- package/lib/ObservedInboundTrack.d.ts.map +1 -1
- package/lib/ObservedInboundTrack.js +39 -2
- package/lib/ObservedPeerConnection.d.ts +39 -1
- package/lib/ObservedPeerConnection.d.ts.map +1 -1
- package/lib/ObservedPeerConnection.js +61 -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 +90 -88
- package/src/ObservedICE.ts +176 -0
- package/src/ObservedInboundTrack.ts +50 -1
- package/src/ObservedPeerConnection.ts +79 -2
- 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/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
|
|
|
@@ -97,11 +96,39 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
97
96
|
private _updated = Date.now();
|
|
98
97
|
private _acceptedSample = 0;
|
|
99
98
|
private _processingSample = 0;
|
|
100
|
-
private _selectedLocalIceCandidate?: IceLocalCandidate;
|
|
101
|
-
private _selectedRemoteIceCandidate?: IceLocalCandidate;
|
|
102
99
|
private _timeZoneOffsetInHours?: number;
|
|
103
100
|
private _leaveReported = false;
|
|
104
101
|
|
|
102
|
+
public totalInboundPacketsLost = 0;
|
|
103
|
+
public totalInboundPacketsReceived = 0;
|
|
104
|
+
public totalOutboundPacketsLost = 0;
|
|
105
|
+
public totalOutboundPacketsReceived = 0;
|
|
106
|
+
public totalOutboundPacketsSent = 0;
|
|
107
|
+
public totalDataChannelBytesSent = 0;
|
|
108
|
+
public totalDataChannelBytesReceived = 0;
|
|
109
|
+
public totalSentAudioBytes = 0;
|
|
110
|
+
public totalSentVideoBytes = 0;
|
|
111
|
+
public totalReceivedAudioBytes = 0;
|
|
112
|
+
public totalReceivedVideoBytes = 0;
|
|
113
|
+
|
|
114
|
+
public deltaInboundPacketsLost?: number;
|
|
115
|
+
public deltaInboundPacketsReceived?: number;
|
|
116
|
+
public deltaOutboundPacketsLost?: number;
|
|
117
|
+
public deltaOutboundPacketsReceived?: number;
|
|
118
|
+
public deltaOutboundPacketsSent?: number;
|
|
119
|
+
public deltaDataChannelBytesSent?: number;
|
|
120
|
+
public deltaDataChannelBytesReceived?: number;
|
|
121
|
+
public deltaReceivedAudioBytes?: number;
|
|
122
|
+
public deltaReceivedVideoBytes?: number;
|
|
123
|
+
public deltaSentAudioBytes?: number;
|
|
124
|
+
public deltaSentVideoBytes?: number;
|
|
125
|
+
|
|
126
|
+
public avgRttInS?: number;
|
|
127
|
+
public sendingAudioBitrate?: number;
|
|
128
|
+
public sendingVideoBitrate?: number;
|
|
129
|
+
public receivingAudioBitrate?: number;
|
|
130
|
+
public receivingVideoBitrate?: number;
|
|
131
|
+
|
|
105
132
|
public readonly mediaDevices: string[] = [];
|
|
106
133
|
public readonly codecs: string[] = [];
|
|
107
134
|
public readonly userMediaErrors: string[] = [];
|
|
@@ -153,14 +180,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
153
180
|
return this._model.userId;
|
|
154
181
|
}
|
|
155
182
|
|
|
156
|
-
public get selectedLocalIceCandidate() {
|
|
157
|
-
return this._selectedLocalIceCandidate;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public get selectedRemoteIceCandidate() {
|
|
161
|
-
return this._selectedRemoteIceCandidate;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
183
|
public get timeZoneOffsetInHours() {
|
|
165
184
|
return this._timeZoneOffsetInHours;
|
|
166
185
|
}
|
|
@@ -380,8 +399,23 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
380
399
|
}
|
|
381
400
|
|
|
382
401
|
for (const { timestamp = Date.now(), ...callEvent } of sample.customCallEvents ?? []) {
|
|
383
|
-
|
|
402
|
+
const peerConnection = this._peerConnections.get(callEvent.peerConnectionId ?? '');
|
|
384
403
|
|
|
404
|
+
switch (callEvent.name) {
|
|
405
|
+
case CallEventType.CLIENT_LEFT:
|
|
406
|
+
this._leaveReported = true;
|
|
407
|
+
break;
|
|
408
|
+
case CallEventType.ICE_CONNECTION_STATE_CHANGED:
|
|
409
|
+
peerConnection && callEvent.value && peerConnection.emit('iceconnectionstatechange', callEvent.value);
|
|
410
|
+
break;
|
|
411
|
+
case CallEventType.ICE_GATHERING_STATE_CHANGED:
|
|
412
|
+
peerConnection && callEvent.value && peerConnection.emit('icegatheringstatechange', callEvent.value);
|
|
413
|
+
break;
|
|
414
|
+
case CallEventType.PEER_CONNECTION_STATE_CHANGED:
|
|
415
|
+
peerConnection && callEvent.value && peerConnection.emit('connectionstatechange', callEvent.value);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
|
|
385
419
|
this.reports.addCallEventReport({
|
|
386
420
|
serviceId: this.serviceId,
|
|
387
421
|
mediaUnitId: this.mediaUnitId,
|
|
@@ -480,76 +514,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
480
514
|
|
|
481
515
|
this.reports.addCallMetaReport(callMetaReport);
|
|
482
516
|
}
|
|
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
517
|
|
|
554
518
|
for (const transport of sample.pcTransports ?? []) {
|
|
555
519
|
try {
|
|
@@ -676,6 +640,48 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
676
640
|
}
|
|
677
641
|
}
|
|
678
642
|
|
|
643
|
+
for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
|
|
644
|
+
if (!iceLocalCandidate.peerConnectionId) {
|
|
645
|
+
logger.warn(`Local ice candidate without peerConnectionId: ${JSON.stringify(iceLocalCandidate)}`);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
const peerConnection = this._peerConnections.get(iceLocalCandidate.peerConnectionId);
|
|
649
|
+
|
|
650
|
+
if (!peerConnection) {
|
|
651
|
+
logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
peerConnection.ICE.addLocalCandidate(iceLocalCandidate);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
|
|
659
|
+
if (!iceRemoteCandidate.peerConnectionId) {
|
|
660
|
+
logger.warn(`Remote ice candidate without peerConnectionId: ${JSON.stringify(iceRemoteCandidate)}`);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
const peerConnection = this._peerConnections.get(iceRemoteCandidate.peerConnectionId);
|
|
664
|
+
|
|
665
|
+
if (!peerConnection) {
|
|
666
|
+
logger.debug(`Peer connection ${iceRemoteCandidate.peerConnectionId} not found for ice remote candidate ${iceRemoteCandidate.id}`);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
for (const candidatePair of sample.iceCandidatePairs ?? []) {
|
|
674
|
+
const peerConnection = this._peerConnections.get(candidatePair.peerConnectionId);
|
|
675
|
+
|
|
676
|
+
if (!peerConnection) {
|
|
677
|
+
logger.debug(`Peer connection ${candidatePair.peerConnectionId} not found for ice candidate pair ${candidatePair.localCandidateId}, ${candidatePair.remoteCandidateId}`);
|
|
678
|
+
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
peerConnection.ICE.update(candidatePair, sample.timestamp);
|
|
683
|
+
}
|
|
684
|
+
|
|
679
685
|
// close entries that are idle for too long
|
|
680
686
|
if (this.call.observer.config.maxEntryIdleTimeInMs && this._updated < sample.timestamp) {
|
|
681
687
|
|
|
@@ -707,10 +713,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
707
713
|
}
|
|
708
714
|
}
|
|
709
715
|
|
|
710
|
-
if (changeIceCandidatePair) {
|
|
711
|
-
this.emit('candidate-pair-change');
|
|
712
|
-
}
|
|
713
|
-
|
|
714
716
|
if (executeSave) await this._save();
|
|
715
717
|
|
|
716
718
|
this._updated = sample.timestamp;
|
|
@@ -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
|
+
}
|
|
@@ -28,6 +28,14 @@ export type ObservedInboundTrackStats<K extends MediaKind> = ObservedInboundTrac
|
|
|
28
28
|
bitrate: number;
|
|
29
29
|
fractionLost: number;
|
|
30
30
|
rttInMs?: number;
|
|
31
|
+
lostPackets?: number;
|
|
32
|
+
receivedPackets?: number;
|
|
33
|
+
receivedFrames?: number;
|
|
34
|
+
decodedFrames?: number;
|
|
35
|
+
droppedFrames?: number;
|
|
36
|
+
receivedSamples?: number;
|
|
37
|
+
silentConcealedSamples?: number;
|
|
38
|
+
fractionLoss?: number;
|
|
31
39
|
};
|
|
32
40
|
|
|
33
41
|
// {
|
|
@@ -76,6 +84,14 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
76
84
|
private _remoteOutboundTrack?: ObservedOutboundTrack<Kind>;
|
|
77
85
|
public bitrate = -1;
|
|
78
86
|
public rttInMs = -1;
|
|
87
|
+
public lostPackets = 0;
|
|
88
|
+
public receivedPackets = 0;
|
|
89
|
+
public receivedFrames = 0;
|
|
90
|
+
public decodedFrames = 0;
|
|
91
|
+
public droppedFrames = 0;
|
|
92
|
+
public receivedSamples = 0;
|
|
93
|
+
public silentConcealedSamples = 0;
|
|
94
|
+
public fractionLoss = 0;
|
|
79
95
|
|
|
80
96
|
private constructor(
|
|
81
97
|
private readonly _model: Models.InboundTrack,
|
|
@@ -211,19 +227,52 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
211
227
|
const lostPackets = (sample.packetsLost ?? 0) - (lastStat?.packetsLost ?? 0);
|
|
212
228
|
const receivedPackets = (sample.packetsReceived ?? 0) - (lastStat?.packetsReceived ?? 0);
|
|
213
229
|
const fractionLost = 0 < receivedPackets && 0 < lostPackets ? (lostPackets / (receivedPackets + lostPackets)) : 0;
|
|
230
|
+
let decodedFrames: number | undefined;
|
|
231
|
+
let droppedFrames: number | undefined;
|
|
232
|
+
let receivedFrames: number | undefined;
|
|
233
|
+
let silentConcealedSamples: number | undefined;
|
|
234
|
+
|
|
235
|
+
if (this.kind === 'video') {
|
|
236
|
+
const videoSample = sample as InboundVideoTrack;
|
|
237
|
+
const lastVideoStat = lastStat as InboundVideoTrack | undefined;
|
|
238
|
+
|
|
239
|
+
decodedFrames = (videoSample.framesDecoded ?? 0) - (lastVideoStat?.framesDecoded ?? 0);
|
|
240
|
+
droppedFrames = (videoSample.framesDropped ?? 0) - (lastVideoStat?.framesDropped ?? 0);
|
|
241
|
+
receivedFrames = (videoSample.framesReceived ?? 0) - (lastVideoStat?.framesReceived ?? 0);
|
|
242
|
+
} else if (this.kind === 'audio') {
|
|
243
|
+
const audioSample = sample as InboundAudioTrack;
|
|
244
|
+
const lastAudioStat = lastStat as InboundAudioTrack | undefined;
|
|
245
|
+
|
|
246
|
+
silentConcealedSamples = (audioSample.silentConcealedSamples ?? 0) - (lastAudioStat?.silentConcealedSamples ?? 0);
|
|
247
|
+
}
|
|
248
|
+
|
|
214
249
|
const stats: ObservedInboundTrackStats<Kind> = {
|
|
215
250
|
...sample,
|
|
216
251
|
fractionLost,
|
|
217
252
|
rttInMs,
|
|
218
253
|
bitrate,
|
|
219
254
|
ssrc: sample.ssrc,
|
|
255
|
+
lostPackets,
|
|
256
|
+
receivedPackets,
|
|
257
|
+
receivedFrames,
|
|
258
|
+
decodedFrames,
|
|
259
|
+
droppedFrames,
|
|
260
|
+
silentConcealedSamples,
|
|
220
261
|
};
|
|
221
262
|
|
|
222
263
|
this._stats.set(sample.ssrc, stats);
|
|
223
264
|
|
|
224
265
|
this.bitrate = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.bitrate, 0);
|
|
225
266
|
this.rttInMs = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.rttInMs ?? 0), 0) / (this._stats.size || 1);
|
|
226
|
-
|
|
267
|
+
this.lostPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.lostPackets ?? 0), 0);
|
|
268
|
+
this.receivedPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedPackets ?? 0), 0);
|
|
269
|
+
this.receivedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedFrames ?? 0), 0);
|
|
270
|
+
this.decodedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.decodedFrames ?? 0), 0);
|
|
271
|
+
this.droppedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.droppedFrames ?? 0), 0);
|
|
272
|
+
this.receivedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedSamples ?? 0), 0);
|
|
273
|
+
this.silentConcealedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.silentConcealedSamples ?? 0), 0);
|
|
274
|
+
this.fractionLoss = 0 < this.receivedPackets && 0 < this.lostPackets ? (this.lostPackets / (this.receivedPackets + this.lostPackets)) : 0;
|
|
275
|
+
|
|
227
276
|
if (executeSave) await this._save();
|
|
228
277
|
|
|
229
278
|
this._updated = timestamp;
|