@observertc/observer-js 1.1.0 โ 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/index.d.mts +295 -43
- package/dist/index.d.ts +295 -43
- package/dist/index.js +473 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +452 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1126,6 +1126,7 @@ counters** โ that is the rule the whole design hangs on:
|
|
|
1126
1126
|
| `PublisherFaultCorroborationDetector` | ๐ | call | `CORROBORATED_PUBLISHER_FAULT` |
|
|
1127
1127
|
| `TrackDeliveryMismatchDetector` | ๐ | call | `PUBLISHED_TRACK_NOT_DELIVERED`, `RECEIVER_TRACK_NOT_DELIVERED`, `PUBLISHER_TRACK_DRY` |
|
|
1128
1128
|
| `UnconsumedTrackDetector` | ๐ | call | `UNCONSUMED_PUBLISHED_TRACK` |
|
|
1129
|
+
| `AudioEchoDetector` | ๐ยน | call | `AUDIO_ECHO_SUSPECTED` |
|
|
1129
1130
|
| `ClientPopulationIssueDetector` | | **observer** | `CLIENT_POPULATION_ISSUE` |
|
|
1130
1131
|
| `SfuCongestionDetector` | | **observer** | `sfu-congestion` |
|
|
1131
1132
|
| `TurnServerHealthDetector` | | **observer** | `TURN_SERVER_DEGRADED` |
|
|
@@ -1168,6 +1169,16 @@ What each adds that no endpoint can know:
|
|
|
1168
1169
|
from the **same SFU** โ the finding's meaning is "these clients share only that server".
|
|
1169
1170
|
- **`TrackDeliveryMismatchDetector`** โ *are the two ends of a track disagreeing?*
|
|
1170
1171
|
- **`UnconsumedTrackDetector`** โ *is anyone actually subscribed?* (reads the resolver's silence)
|
|
1172
|
+
- **`AudioEchoDetector`** โ *is someone sending the others' voices back into the call?* Compares
|
|
1173
|
+
each outbound audio track's per-tick level (`ObservedOutboundTrack.rmsAudioLevel`) with the
|
|
1174
|
+
loudest other participant's, bucket by bucket. An echo is active whenever the source speaks, and
|
|
1175
|
+
louder when the source is louder; turn-taking fails the first test and cross-talk the second.
|
|
1176
|
+
When both tracks of a pair pass, it reports the one with the client's `loopback-audio-input`
|
|
1177
|
+
issue open, else the one that follows more reliably, else the quieter one (`ambiguousDirection`).
|
|
1178
|
+
Works at the clients' sampling granularity, so it catches sustained echo (a `Monitor of โฆ`
|
|
1179
|
+
loopback device, speakers into an open mic) rather than faint echo.
|
|
1180
|
+
ยน The resolver is needed only for publishers whose browser reports no media-source audio level
|
|
1181
|
+
(Firefox): their level is then read from what their receivers heard.
|
|
1171
1182
|
- **`TurnServerHealthDetector`** โ *does trouble cluster on one relay?*
|
|
1172
1183
|
- **`TurnServerOutageDetector`** โ covers the case the health detector structurally cannot. The
|
|
1173
1184
|
health detector groups clients by the server relaying them and asks how many report issues โ it
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { types } from 'mediasoup';
|
|
3
3
|
|
|
4
4
|
declare const schemaVersion = "3.7.0";
|
|
@@ -1479,6 +1479,16 @@ declare class ObservedMediaSource implements MediaSourceStats {
|
|
|
1479
1479
|
frames?: number | undefined;
|
|
1480
1480
|
framesPerSecond?: number | undefined;
|
|
1481
1481
|
attachments?: Record<string, unknown> | undefined;
|
|
1482
|
+
/** Increase of `totalAudioEnergy` in this tick (audio only). */
|
|
1483
|
+
deltaTotalAudioEnergy: number;
|
|
1484
|
+
/** Increase of `totalSamplesDuration` in this tick, in seconds (audio only). */
|
|
1485
|
+
deltaTotalSamplesDuration: number;
|
|
1486
|
+
/**
|
|
1487
|
+
* RMS level (0..1) of what the capture device delivered over the tick, after the browser's own
|
|
1488
|
+
* processing (AEC, NS, AGC). Chrome and Safari report the inputs; Firefox does not, and there the
|
|
1489
|
+
* publisher's level has to come from its receivers โ see `ObservedOutboundTrack.rmsAudioLevel`.
|
|
1490
|
+
*/
|
|
1491
|
+
rmsAudioLevel?: number;
|
|
1482
1492
|
constructor(timestamp: number, id: string, kind: MediaKind, _peerConnection: ObservedPeerConnection);
|
|
1483
1493
|
get visited(): boolean;
|
|
1484
1494
|
getPeerConnection(): ObservedPeerConnection;
|
|
@@ -1932,6 +1942,65 @@ declare function mannKendall(values: number[], alpha?: number): MannKendallResul
|
|
|
1932
1942
|
* recomputing every pair from scratch) doesn't have to reimplement the normal approximation.
|
|
1933
1943
|
*/
|
|
1934
1944
|
declare function mannKendallVerdict(s: number, variance: number, alpha?: number): Pick<MannKendallResult, 'z' | 'pValue' | 'trend'>;
|
|
1945
|
+
/**
|
|
1946
|
+
* Estimates the p-th percentile (0..1) of the values counted in `histogram`, whose bucket `i`
|
|
1947
|
+
* holds the values in `[i * bucketWidth, (i + 1) * bucketWidth)`. Interpolates linearly inside
|
|
1948
|
+
* the bucket the rank falls in, so the error is at most one bucket width. With `openEnded`, the
|
|
1949
|
+
* last bucket holds every value above its lower bound, and a percentile falling in it is reported
|
|
1950
|
+
* as `max`. The estimate is clamped to `[min, max]` when given. `undefined` for an empty histogram.
|
|
1951
|
+
*/
|
|
1952
|
+
declare function percentileOfHistogram(histogram: readonly number[], bucketWidth: number, p: number, options?: {
|
|
1953
|
+
min?: number;
|
|
1954
|
+
max?: number;
|
|
1955
|
+
openEnded?: boolean;
|
|
1956
|
+
}): number | undefined;
|
|
1957
|
+
|
|
1958
|
+
declare enum ClientMetaTypes {
|
|
1959
|
+
MEDIA_CONSTRAINT = "MEDIA_CONSTRAINT",
|
|
1960
|
+
MEDIA_DEVICE = "MEDIA_DEVICE",
|
|
1961
|
+
MEDIA_DEVICES_SUPPORTED_CONSTRAINTS = "MEDIA_DEVICES_SUPPORTED_CONSTRAINTS",
|
|
1962
|
+
USER_MEDIA_ERROR = "USER_MEDIA_ERROR",
|
|
1963
|
+
LOCAL_SDP = "LOCAL_SDP",
|
|
1964
|
+
AUDIO_INPUT_DEVICE = "AUDIO_INPUT_DEVICE",
|
|
1965
|
+
OPERATION_SYSTEM = "OPERATION_SYSTEM",
|
|
1966
|
+
ENGINE = "ENGINE",
|
|
1967
|
+
PLATFORM = "PLATFORM",
|
|
1968
|
+
BROWSER = "BROWSER"
|
|
1969
|
+
}
|
|
1970
|
+
type MediaDeviceInfo = {
|
|
1971
|
+
deviceId: string;
|
|
1972
|
+
label: string;
|
|
1973
|
+
kind: string;
|
|
1974
|
+
groupId: string;
|
|
1975
|
+
};
|
|
1976
|
+
/**
|
|
1977
|
+
* Payload of `AUDIO_INPUT_DEVICE`: which device an outbound audio track captures from. Sent by the
|
|
1978
|
+
* client once per track, when it is first monitored.
|
|
1979
|
+
*/
|
|
1980
|
+
type AudioInputDevice = {
|
|
1981
|
+
peerConnectionId: string;
|
|
1982
|
+
trackId: string;
|
|
1983
|
+
/** `MediaStreamTrack.label`; empty when capture permission was not yet granted. */
|
|
1984
|
+
label: string;
|
|
1985
|
+
deviceId?: string;
|
|
1986
|
+
groupId?: string;
|
|
1987
|
+
};
|
|
1988
|
+
type Browser = {
|
|
1989
|
+
name: string;
|
|
1990
|
+
version: string;
|
|
1991
|
+
};
|
|
1992
|
+
type Platform = {
|
|
1993
|
+
name: string;
|
|
1994
|
+
version: string;
|
|
1995
|
+
};
|
|
1996
|
+
type Engine = {
|
|
1997
|
+
name: string;
|
|
1998
|
+
version: string;
|
|
1999
|
+
};
|
|
2000
|
+
type OperationSystem = {
|
|
2001
|
+
name: string;
|
|
2002
|
+
version: string;
|
|
2003
|
+
};
|
|
1935
2004
|
|
|
1936
2005
|
/** The per-receiver view the aggregator builds for one subscribed track. */
|
|
1937
2006
|
type PublishedTrackReceivingDistributionEntry = {
|
|
@@ -1983,6 +2052,20 @@ declare class ObservedOutboundTrack implements OutboundTrackSample {
|
|
|
1983
2052
|
remoteFractionLost?: number | undefined;
|
|
1984
2053
|
remoteRttInMs?: number | undefined;
|
|
1985
2054
|
qualityLimitationReason?: string | undefined;
|
|
2055
|
+
/**
|
|
2056
|
+
* This audio track's RMS level (0..1) over the latest tick โ how loud the publisher was sending.
|
|
2057
|
+
* Taken from the publisher's own `media-source` when it reports one (Chrome, Safari), otherwise
|
|
2058
|
+
* the median of what its receivers heard ({@link receivedRmsAudioLevel}), which is what makes it
|
|
2059
|
+
* available for Firefox publishers too. `undefined` for video, or when neither is known.
|
|
2060
|
+
* {@link rmsAudioLevelSource} says which one it is.
|
|
2061
|
+
*/
|
|
2062
|
+
rmsAudioLevel?: number;
|
|
2063
|
+
rmsAudioLevelSource?: 'media-source' | 'receivers';
|
|
2064
|
+
/**
|
|
2065
|
+
* Median RMS level (0..1) of this track across the receivers that played it out, each over its own
|
|
2066
|
+
* latest tick. Needs a `RemoteTrackResolver`: without links there are no receivers to ask.
|
|
2067
|
+
*/
|
|
2068
|
+
receivedRmsAudioLevel?: number;
|
|
1986
2069
|
constructor(timestamp: number, id: string, kind: MediaKind, _peerConnection: ObservedPeerConnection, _outboundRtps?: ObservedOutboundRtp[] | undefined, _mediaSource?: ObservedMediaSource | undefined);
|
|
1987
2070
|
get score(): number | undefined;
|
|
1988
2071
|
get visited(): boolean;
|
|
@@ -1990,7 +2073,10 @@ declare class ObservedOutboundTrack implements OutboundTrackSample {
|
|
|
1990
2073
|
getPeerConnection(): ObservedPeerConnection;
|
|
1991
2074
|
getOutboundRtps(): ObservedOutboundRtp[] | undefined;
|
|
1992
2075
|
getMediaSource(): ObservedMediaSource | undefined;
|
|
2076
|
+
/** The capture device behind this track, when the client sent `AUDIO_INPUT_DEVICE` metadata for it. */
|
|
2077
|
+
get audioInputDevice(): AudioInputDevice | undefined;
|
|
1993
2078
|
update(stats: OutboundTrackSample): void;
|
|
2079
|
+
private _updateAudioLevel;
|
|
1994
2080
|
private createReceivingDistribution;
|
|
1995
2081
|
}
|
|
1996
2082
|
|
|
@@ -2146,6 +2232,15 @@ declare class ObservedInboundRtp implements InboundRtpStats {
|
|
|
2146
2232
|
deltaFecPacketsReceived: number;
|
|
2147
2233
|
deltaFecPacketsDiscarded: number;
|
|
2148
2234
|
deltaPausesDuration: number;
|
|
2235
|
+
deltaTotalAudioEnergy: number;
|
|
2236
|
+
deltaTotalSamplesDuration: number;
|
|
2237
|
+
/**
|
|
2238
|
+
* RMS level (0..1) of the audio played out from this stream over the tick, from the two deltas
|
|
2239
|
+
* above. This is the far end's outgoing level **as this receiver heard it**, which is the only
|
|
2240
|
+
* send-side level available for publishers on browsers that do not report `media-source` audio
|
|
2241
|
+
* levels (Firefox). `undefined` for video and for a tick with no audio accounted for.
|
|
2242
|
+
*/
|
|
2243
|
+
rmsAudioLevel?: number;
|
|
2149
2244
|
/**
|
|
2150
2245
|
* Mean jitter-buffer delay for the frames/samples emitted in this tick (seconds), derived from
|
|
2151
2246
|
* the cumulative `jitterBufferDelay` / `jitterBufferEmittedCount` pair โ the only correct way to
|
|
@@ -2447,40 +2542,6 @@ declare class ObservedPeerConnection extends EventEmitter {
|
|
|
2447
2542
|
private _notify;
|
|
2448
2543
|
}
|
|
2449
2544
|
|
|
2450
|
-
declare enum ClientMetaTypes {
|
|
2451
|
-
MEDIA_CONSTRAINT = "MEDIA_CONSTRAINT",
|
|
2452
|
-
MEDIA_DEVICE = "MEDIA_DEVICE",
|
|
2453
|
-
MEDIA_DEVICES_SUPPORTED_CONSTRAINTS = "MEDIA_DEVICES_SUPPORTED_CONSTRAINTS",
|
|
2454
|
-
USER_MEDIA_ERROR = "USER_MEDIA_ERROR",
|
|
2455
|
-
LOCAL_SDP = "LOCAL_SDP",
|
|
2456
|
-
OPERATION_SYSTEM = "OPERATION_SYSTEM",
|
|
2457
|
-
ENGINE = "ENGINE",
|
|
2458
|
-
PLATFORM = "PLATFORM",
|
|
2459
|
-
BROWSER = "BROWSER"
|
|
2460
|
-
}
|
|
2461
|
-
type MediaDeviceInfo = {
|
|
2462
|
-
deviceId: string;
|
|
2463
|
-
label: string;
|
|
2464
|
-
kind: string;
|
|
2465
|
-
groupId: string;
|
|
2466
|
-
};
|
|
2467
|
-
type Browser = {
|
|
2468
|
-
name: string;
|
|
2469
|
-
version: string;
|
|
2470
|
-
};
|
|
2471
|
-
type Platform = {
|
|
2472
|
-
name: string;
|
|
2473
|
-
version: string;
|
|
2474
|
-
};
|
|
2475
|
-
type Engine = {
|
|
2476
|
-
name: string;
|
|
2477
|
-
version: string;
|
|
2478
|
-
};
|
|
2479
|
-
type OperationSystem = {
|
|
2480
|
-
name: string;
|
|
2481
|
-
version: string;
|
|
2482
|
-
};
|
|
2483
|
-
|
|
2484
2545
|
/**
|
|
2485
2546
|
* Turning a correlation into a **conclusion**.
|
|
2486
2547
|
*
|
|
@@ -3648,6 +3709,31 @@ declare class ObservedClientIssueRegistry {
|
|
|
3648
3709
|
clear(): void;
|
|
3649
3710
|
}
|
|
3650
3711
|
|
|
3712
|
+
/** Width of one `rttHistogram` bucket. */
|
|
3713
|
+
declare const RTT_HISTOGRAM_BUCKET_WIDTH_IN_MS = 20;
|
|
3714
|
+
/** Number of `rttHistogram` buckets: 0โ960 ms in 20 ms steps, plus one bucket for 980 ms and above. */
|
|
3715
|
+
declare const RTT_HISTOGRAM_NUMBER_OF_BUCKETS = 50;
|
|
3716
|
+
/** Width of one `scoreHistogram` bucket. Scores are 0..5. */
|
|
3717
|
+
declare const SCORE_HISTOGRAM_BUCKET_WIDTH = 1;
|
|
3718
|
+
/** Number of `scoreHistogram` buckets: [0, 1), [1, 2), [2, 3), [3, 4) and [4, 5]. */
|
|
3719
|
+
declare const SCORE_HISTOGRAM_NUMBER_OF_BUCKETS = 5;
|
|
3720
|
+
/** Session RTT distribution of a client, in milliseconds, estimated from `rttHistogram`. */
|
|
3721
|
+
type RttSummary = {
|
|
3722
|
+
p50: number | undefined;
|
|
3723
|
+
p75: number | undefined;
|
|
3724
|
+
p95: number | undefined;
|
|
3725
|
+
p99: number | undefined;
|
|
3726
|
+
avg: number | undefined;
|
|
3727
|
+
};
|
|
3728
|
+
/** Session score distribution of a client, estimated from `scoreHistogram`. */
|
|
3729
|
+
type ScoreSummary = {
|
|
3730
|
+
p5: number | undefined;
|
|
3731
|
+
p25: number | undefined;
|
|
3732
|
+
p50: number | undefined;
|
|
3733
|
+
p75: number | undefined;
|
|
3734
|
+
p95: number | undefined;
|
|
3735
|
+
avg: number | undefined;
|
|
3736
|
+
};
|
|
3651
3737
|
type ObservedClientSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = {
|
|
3652
3738
|
clientId: string;
|
|
3653
3739
|
appData?: AppData;
|
|
@@ -3707,10 +3793,6 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
3707
3793
|
deltaOutboundPacketsSent: number;
|
|
3708
3794
|
deltaTransportSentBytes: number;
|
|
3709
3795
|
deltaTransportReceivedBytes: number;
|
|
3710
|
-
deltaRttLt50Measurements: number;
|
|
3711
|
-
deltaRttLt150Measurements: number;
|
|
3712
|
-
deltaRttLt300Measurements: number;
|
|
3713
|
-
deltaRttGtOrEq300Measurements: number;
|
|
3714
3796
|
currentMaxRttInMs?: number;
|
|
3715
3797
|
currentMinRttInMs?: number;
|
|
3716
3798
|
currentAvgRttInMs?: number;
|
|
@@ -3726,7 +3808,67 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
3726
3808
|
deltaNumberOfIssues: number;
|
|
3727
3809
|
totalScoreSum: number;
|
|
3728
3810
|
numberOfScoreMeasurements: number;
|
|
3811
|
+
minScore?: number;
|
|
3812
|
+
maxScore?: number;
|
|
3813
|
+
/**
|
|
3814
|
+
* Score measurements (one per accepted sample with a score) counted into
|
|
3815
|
+
* `SCORE_HISTOGRAM_NUMBER_OF_BUCKETS` buckets of `SCORE_HISTOGRAM_BUCKET_WIDTH`. Histograms of
|
|
3816
|
+
* several clients can be added bucket by bucket to get the distribution of a call.
|
|
3817
|
+
*/
|
|
3818
|
+
readonly scoreHistogram: number[];
|
|
3819
|
+
/** Issues raised over the session, counted as in `issueHistogram`. */
|
|
3820
|
+
totalNumberOfIssues: number;
|
|
3821
|
+
/**
|
|
3822
|
+
* Number of times each issue type was raised over the session, keyed by the base type (without
|
|
3823
|
+
* the `-resolved` suffix): `client.issueHistogram['loopback-audio-input']`. A resolution entry is
|
|
3824
|
+
* not counted, and neither is a re-raise of a keyed issue that is still open, so a stateful issue
|
|
3825
|
+
* reported on every tick counts once per open interval. A type never raised is `undefined`.
|
|
3826
|
+
*/
|
|
3827
|
+
readonly issueHistogram: Record<string, number>;
|
|
3828
|
+
totalRttSumInMs: number;
|
|
3829
|
+
numberOfRttMeasurements: number;
|
|
3830
|
+
maxRttInMs?: number;
|
|
3831
|
+
minRttInMs?: number;
|
|
3832
|
+
/**
|
|
3833
|
+
* RTT measurements counted into fixed buckets of `RTT_HISTOGRAM_BUCKET_WIDTH_IN_MS`; the last bucket
|
|
3834
|
+
* holds every measurement of 980 ms and above. Constant memory, so percentiles can be estimated
|
|
3835
|
+
* over the whole session (see `rttSummary`) without keeping every measurement. Histograms
|
|
3836
|
+
* of several clients can be added bucket by bucket to get the distribution of a call.
|
|
3837
|
+
*/
|
|
3838
|
+
readonly rttHistogram: number[];
|
|
3839
|
+
private _rttSummary?;
|
|
3840
|
+
private _scoreSummary?;
|
|
3841
|
+
totalInboundPacketsLost: number;
|
|
3842
|
+
totalInboundPacketsReceived: number;
|
|
3843
|
+
totalOutboundPacketsSent: number;
|
|
3844
|
+
totalReceivedAudioBytes: number;
|
|
3845
|
+
totalReceivedVideoBytes: number;
|
|
3846
|
+
totalSentAudioBytes: number;
|
|
3847
|
+
totalSentVideoBytes: number;
|
|
3848
|
+
totalTransportReceivedBytes: number;
|
|
3849
|
+
totalTransportSentBytes: number;
|
|
3850
|
+
totalDataChannelBytesReceived: number;
|
|
3851
|
+
totalDataChannelBytesSent: number;
|
|
3852
|
+
totalDataChannelMessagesReceived: number;
|
|
3853
|
+
totalDataChannelMessagesSent: number;
|
|
3854
|
+
/** Highest `sendingAudioBitrate + sendingVideoBitrate` of any sample, in bits per second. */
|
|
3855
|
+
peakSendingBitrate: number;
|
|
3856
|
+
/** Highest `receivingAudioBitrate + receivingVideoBitrate` of any sample, in bits per second. */
|
|
3857
|
+
peakReceivingBitrate: number;
|
|
3858
|
+
numberOfSamplesOverUdp: number;
|
|
3859
|
+
numberOfSamplesOverTcp: number;
|
|
3860
|
+
numberOfSamplesOverTurnUdp: number;
|
|
3861
|
+
numberOfSamplesOverTurnTcp: number;
|
|
3862
|
+
numberOfSamplesWithUnknownPath: number;
|
|
3863
|
+
numberOfPublicAddressChanges: number;
|
|
3729
3864
|
readonly mediaDevices: MediaDeviceInfo[];
|
|
3865
|
+
/**
|
|
3866
|
+
* The capture device behind each outbound audio track, keyed by track id, from the client's
|
|
3867
|
+
* `AUDIO_INPUT_DEVICE` metadata. Kept for the client's lifetime (a device switch is a new track
|
|
3868
|
+
* id), so a finding raised after a track ended can still name its device. Read it per track via
|
|
3869
|
+
* `ObservedOutboundTrack.audioInputDevice`.
|
|
3870
|
+
*/
|
|
3871
|
+
readonly audioInputDevices: Map<string, AudioInputDevice>;
|
|
3730
3872
|
/**
|
|
3731
3873
|
* The client's currently **open** stateful issues, keyed by `ClientIssue.key` โ the server-side
|
|
3732
3874
|
* mirror of the client monitor's own active-issue map (client-monitor-js >= 4.6.0).
|
|
@@ -3739,9 +3881,7 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
3739
3881
|
readonly activeIssues: ObservedClientIssueRegistry;
|
|
3740
3882
|
private _pendingInjections;
|
|
3741
3883
|
private _activeSample?;
|
|
3742
|
-
/** Peer connections the client reported closed (`PEER_CONNECTION_CLOSED`), closed once the current sample is applied. */
|
|
3743
3884
|
private readonly _peerConnectionsToClose;
|
|
3744
|
-
/** Ids of peer connections closed by the client; late stats for them are ignored rather than resurrecting them. */
|
|
3745
3885
|
private readonly _closedPeerConnectionIds;
|
|
3746
3886
|
private closeTimer?;
|
|
3747
3887
|
constructor(settings: ObservedClientSettings<AppData>, call: ObservedCall, activeIssues: ObservedClientIssueRegistry);
|
|
@@ -3749,6 +3889,18 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
3749
3889
|
get publicAddresses(): string[];
|
|
3750
3890
|
get numberOfPeerConnections(): number;
|
|
3751
3891
|
get score(): number | undefined;
|
|
3892
|
+
/**
|
|
3893
|
+
* Percentiles (error at most one 20 ms bucket; a percentile in the overflow bucket is reported as
|
|
3894
|
+
* `maxRttInMs`) and mean of every RTT measurement of the session. Cached until the next measurement.
|
|
3895
|
+
*/
|
|
3896
|
+
get rttSummary(): Readonly<RttSummary>;
|
|
3897
|
+
/**
|
|
3898
|
+
* Percentiles (error at most one score point) and mean of every score of the session. Cached until
|
|
3899
|
+
* the next score.
|
|
3900
|
+
*/
|
|
3901
|
+
get scoreSummary(): Readonly<ScoreSummary>;
|
|
3902
|
+
private _addScoreMeasurement;
|
|
3903
|
+
private _addRttMeasurement;
|
|
3752
3904
|
close(): void;
|
|
3753
3905
|
accept(sample: ClientSample, context?: AcceptContext): void;
|
|
3754
3906
|
injectMetaData(metaData: ClientMetaData): void;
|
|
@@ -5219,6 +5371,105 @@ declare class UnconsumedTrackDetector implements Detector {
|
|
|
5219
5371
|
close(): void;
|
|
5220
5372
|
}
|
|
5221
5373
|
|
|
5374
|
+
declare const AudioEchoTypes: {
|
|
5375
|
+
/**
|
|
5376
|
+
* One participant's outgoing audio follows another participant's voice: it rises when they speak,
|
|
5377
|
+
* in proportion to how loud they are. The echoing participant is sending the far end's playout
|
|
5378
|
+
* back into the call โ speakers into an open mic, or a loopback capture device.
|
|
5379
|
+
*/
|
|
5380
|
+
readonly audioEchoSuspected: "AUDIO_ECHO_SUSPECTED";
|
|
5381
|
+
};
|
|
5382
|
+
type AudioEchoDetectorConfig = {
|
|
5383
|
+
/**
|
|
5384
|
+
* Width of one comparison bucket in ms, on the observer clock. Default `5_000`, the usual
|
|
5385
|
+
* client-monitor sampling period. Each track contributes the level of the last tick seen in the
|
|
5386
|
+
* bucket, so a bucket narrower than the clients' sampling period is mostly empty.
|
|
5387
|
+
*/
|
|
5388
|
+
bucketMs: number;
|
|
5389
|
+
/** Buckets kept per track, and so the stretch the verdict is taken over. Default `24` (two minutes at 5 s). */
|
|
5390
|
+
windowBuckets: number;
|
|
5391
|
+
/**
|
|
5392
|
+
* RMS level (0..1) above which a bucket counts as someone speaking. Default `0.01` (-40 dBFS):
|
|
5393
|
+
* speech after AGC sits around 0.03โ0.1, a quiet room's noise floor well below 0.003.
|
|
5394
|
+
*/
|
|
5395
|
+
activeRmsLevel: number;
|
|
5396
|
+
/**
|
|
5397
|
+
* Buckets in which the loudest *other* participant spoke, required before a track is judged.
|
|
5398
|
+
* Default `8`. With fewer, a couple of overlapping sentences look like an echo.
|
|
5399
|
+
*/
|
|
5400
|
+
minSourceActiveBuckets: number;
|
|
5401
|
+
/**
|
|
5402
|
+
* Share of those buckets in which the suspect track was active too. Default `0.8`. On its own this
|
|
5403
|
+
* is not evidence โ two people talking over each other in the same 5 s score high โ which is why
|
|
5404
|
+
* {@link minCorrelation} has to hold as well.
|
|
5405
|
+
*/
|
|
5406
|
+
minFollowRatio: number;
|
|
5407
|
+
/**
|
|
5408
|
+
* Pearson correlation, over those same buckets, between the suspect's level and the loudest other
|
|
5409
|
+
* participant's level, both in dB. Default `0.6`. This is the discriminating signal: an echo is
|
|
5410
|
+
* louder when the source is louder, two independent speakers are not.
|
|
5411
|
+
*/
|
|
5412
|
+
minCorrelation: number;
|
|
5413
|
+
/** Re-arm time per suspect track in ms. Default `120_000`. */
|
|
5414
|
+
cooldownMs: number;
|
|
5415
|
+
/**
|
|
5416
|
+
* The client issue type the loopback-device check raises, `'loopback-audio-input'` from
|
|
5417
|
+
* client-monitor-js. Only read, never required: when it is open on the suspect track the finding
|
|
5418
|
+
* says so, because it names the cause.
|
|
5419
|
+
*/
|
|
5420
|
+
loopbackAudioInputIssueType: string;
|
|
5421
|
+
};
|
|
5422
|
+
/**
|
|
5423
|
+
* Answers **"is someone sending the others' voices back into the call?"** โ the echo one participant
|
|
5424
|
+
* causes and everyone else hears, which no endpoint can see about itself.
|
|
5425
|
+
*
|
|
5426
|
+
* Every outbound audio track's level is taken once per tick: `ObservedOutboundTrack.rmsAudioLevel`,
|
|
5427
|
+
* the publisher's own media-source level where the browser reports it, otherwise the median of what
|
|
5428
|
+
* its receivers heard. Levels are bucketed on the observer clock, and each track is compared with
|
|
5429
|
+
* the **loudest other participant** in the same bucket rather than with every participant in turn,
|
|
5430
|
+
* which keeps a tick linear in the number of publishers.
|
|
5431
|
+
*
|
|
5432
|
+
* A track is suspected when, over the buckets in which someone else was speaking:
|
|
5433
|
+
*
|
|
5434
|
+
* - it was active in at least `minFollowRatio` of them, **and**
|
|
5435
|
+
* - its level in dB correlates with theirs at `minCorrelation` or above.
|
|
5436
|
+
*
|
|
5437
|
+
* Ordinary turn-taking fails the first test (the listener is quiet while the other talks); cross-talk
|
|
5438
|
+
* passes the first and fails the second (two independent voices do not rise and fall together). The
|
|
5439
|
+
* echoing participant's own speech does not hurt: it fills buckets in which the others are quiet,
|
|
5440
|
+
* and those are not scored.
|
|
5441
|
+
*
|
|
5442
|
+
* An echo and its source correlate in both directions, so when the source rarely has the floor to
|
|
5443
|
+
* themselves both tracks pass. Only one is reported: the one with the client's `loopback-audio-input`
|
|
5444
|
+
* issue open, else the one that follows the other more reliably, else the quieter one โ flagged
|
|
5445
|
+
* `ambiguousDirection` in that last case.
|
|
5446
|
+
*
|
|
5447
|
+
* The source is reported as the participant most often loudest in those buckets. When the suspect
|
|
5448
|
+
* track also has the client's `loopback-audio-input` issue open, the finding says so: that names
|
|
5449
|
+
* the cause, where the correlation alone only shows the effect.
|
|
5450
|
+
*
|
|
5451
|
+
* **Limits.** The comparison is at the clients' sampling granularity (5 s by default), so it catches
|
|
5452
|
+
* sustained echo โ a loopback device, speakers into a laptop mic without AEC โ and misses faint or
|
|
5453
|
+
* intermittent echo. Clients sample on their own schedules, so the buckets of two participants are
|
|
5454
|
+
* offset by up to one sampling period; `minSourceActiveBuckets` is what absorbs that. For Firefox
|
|
5455
|
+
* publishers the level comes from receivers, which needs a `RemoteTrackResolver`; without one those
|
|
5456
|
+
* tracks carry no level and are never judged.
|
|
5457
|
+
*/
|
|
5458
|
+
declare class AudioEchoDetector implements Detector {
|
|
5459
|
+
private readonly call;
|
|
5460
|
+
static readonly NAME: "audio-echo-detector";
|
|
5461
|
+
readonly name: "audio-echo-detector";
|
|
5462
|
+
private readonly _config;
|
|
5463
|
+
private readonly _series;
|
|
5464
|
+
private readonly _lastRaisedAt;
|
|
5465
|
+
private _lastEvaluatedBucket?;
|
|
5466
|
+
constructor(call: ObservedCall, config?: Partial<AudioEchoDetectorConfig>);
|
|
5467
|
+
close(): void;
|
|
5468
|
+
update(): void;
|
|
5469
|
+
private _collect;
|
|
5470
|
+
private _evaluate;
|
|
5471
|
+
}
|
|
5472
|
+
|
|
5222
5473
|
/**
|
|
5223
5474
|
* Detectors that reason **across calls**, created once on the observer.
|
|
5224
5475
|
*
|
|
@@ -5247,6 +5498,7 @@ type AvailableCallScopeDetectorsConfigs = {
|
|
|
5247
5498
|
[CallConcurrentIssueDetector.NAME]: CallConcurrentIssueDetectorConfig;
|
|
5248
5499
|
[IssueFanOutDetector.NAME]: IssueFanOutDetectorConfig;
|
|
5249
5500
|
[PublisherFaultCorroborationDetector.NAME]: PublisherFaultCorroborationDetectorConfig;
|
|
5501
|
+
[AudioEchoDetector.NAME]: AudioEchoDetectorConfig;
|
|
5250
5502
|
};
|
|
5251
5503
|
type AvailableDetectorsConfigs = AvailableObserverScopeDetectorsConfigs | AvailableCallScopeDetectorsConfigs;
|
|
5252
5504
|
declare class Detectors {
|
|
@@ -6292,7 +6544,7 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
6292
6544
|
* });
|
|
6293
6545
|
* ```
|
|
6294
6546
|
*/
|
|
6295
|
-
readonly callDetectorConfigs: Map<keyof AvailableCallScopeDetectorsConfigs, Partial<UnconsumedTrackDetectorConfig | TrackDeliveryMismatchDetectorConfig | CallConcurrentIssueDetectorConfig | IssueFanOutDetectorConfig | PublisherFaultCorroborationDetectorConfig>>;
|
|
6547
|
+
readonly callDetectorConfigs: Map<keyof AvailableCallScopeDetectorsConfigs, Partial<UnconsumedTrackDetectorConfig | TrackDeliveryMismatchDetectorConfig | CallConcurrentIssueDetectorConfig | IssueFanOutDetectorConfig | PublisherFaultCorroborationDetectorConfig | AudioEchoDetectorConfig>>;
|
|
6296
6548
|
/**
|
|
6297
6549
|
* Owns every call's summary: the resolved `config.callSummary`, the bus subscriptions that keep
|
|
6298
6550
|
* the summaries current (one per event type, not one per call), and the summaries themselves.
|
|
@@ -6839,4 +7091,4 @@ declare function createInMemorySink(samples?: ClientSample[]): InMemorySink;
|
|
|
6839
7091
|
declare function createDefaultMediasoupRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
6840
7092
|
declare function createP2pRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
6841
7093
|
|
|
6842
|
-
export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CalculatedScore, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssue, type CallIssueSpread, type CallScopedEventName, type CallSummary, type CallSummaryClients, CallSummaryCollector, type CallSummaryConfig, type CallSummaryEnricher, type CallSummaryEnrichers, type CallSummaryScores, type CallSummarySection, type CallSummaryTruncation, type CallSummaryTurnServers, type CertificateStats, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientLocation, type ClientLocationResolver, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, ClientSampleSink, type ClientSampleSinkEvents, type ClientSampleSinkFactory, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CodecStats, type CorroboratedPublisherFault, type DataChannelStats, type Detector, Detectors, type ExtensionStat, GEOHASH_CELL_SIZES, type IceCandidatePairStats, type IceCandidateStats, type IceTransportStats, InMemorySink, type InboundRtpStats, type InboundTrackSample, type Issue, type IssueBase, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkFactoryOptions, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediaKind, type MediaPlayoutStats, type MediaSourceStats, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, MiddlewareProcessor, ObservedCall, type ObservedCallScope, type ObservedCallSettings, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, type ObservedClientSettings, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, ObservedTURN, type ObservedTURNEventMap, ObservedTurnServer, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type OutboundRtpStats, type OutboundTrackSample, type PageHinkleyResult, type PeerConnectionSample, type PeerConnectionTransportStats, type PsnrSum, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, type QualityLimitationDurations, RESOLVED_ISSUE_SUFFIX, type RemoteInboundRtpStats, type RemoteOutboundRtpStats, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RunningValidator, type SampleRejectedReason, type ScoreCalculator, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createCallSummary, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createJsonlFileSinkFactory, createLogger, createP2pRemoteTrackResolverFactory, defaultCallSummaryConfig, defaultClientHealthThresholds, geohash, isClientIssueResolutionEntry, isPublicIpAddress, issuePayloadAsString, mannKendall, mannKendallVerdict, median, medianAbsoluteDeviation, pageHinkley, percentile, percentileOfSorted, robustZScore, schemaVersion, setObserverLogger, summarize };
|
|
7094
|
+
export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, AudioEchoDetector, type AudioEchoDetectorConfig, AudioEchoTypes, type AudioInputDevice, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CalculatedScore, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssue, type CallIssueSpread, type CallScopedEventName, type CallSummary, type CallSummaryClients, CallSummaryCollector, type CallSummaryConfig, type CallSummaryEnricher, type CallSummaryEnrichers, type CallSummaryScores, type CallSummarySection, type CallSummaryTruncation, type CallSummaryTurnServers, type CertificateStats, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientLocation, type ClientLocationResolver, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, ClientSampleSink, type ClientSampleSinkEvents, type ClientSampleSinkFactory, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CodecStats, type CorroboratedPublisherFault, type DataChannelStats, type Detector, Detectors, type ExtensionStat, GEOHASH_CELL_SIZES, type IceCandidatePairStats, type IceCandidateStats, type IceTransportStats, InMemorySink, type InboundRtpStats, type InboundTrackSample, type Issue, type IssueBase, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkFactoryOptions, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediaKind, type MediaPlayoutStats, type MediaSourceStats, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, MiddlewareProcessor, ObservedCall, type ObservedCallScope, type ObservedCallSettings, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, type ObservedClientSettings, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, ObservedTURN, type ObservedTURNEventMap, ObservedTurnServer, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type OutboundRtpStats, type OutboundTrackSample, type PageHinkleyResult, type PeerConnectionSample, type PeerConnectionTransportStats, type PsnrSum, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, type QualityLimitationDurations, RESOLVED_ISSUE_SUFFIX, RTT_HISTOGRAM_BUCKET_WIDTH_IN_MS, RTT_HISTOGRAM_NUMBER_OF_BUCKETS, type RemoteInboundRtpStats, type RemoteOutboundRtpStats, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RttSummary, type RunningValidator, SCORE_HISTOGRAM_BUCKET_WIDTH, SCORE_HISTOGRAM_NUMBER_OF_BUCKETS, type SampleRejectedReason, type ScoreCalculator, type ScoreSummary, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createCallSummary, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createJsonlFileSinkFactory, createLogger, createP2pRemoteTrackResolverFactory, defaultCallSummaryConfig, defaultClientHealthThresholds, geohash, isClientIssueResolutionEntry, isPublicIpAddress, issuePayloadAsString, mannKendall, mannKendallVerdict, median, medianAbsoluteDeviation, pageHinkley, percentile, percentileOfHistogram, percentileOfSorted, robustZScore, schemaVersion, setObserverLogger, summarize };
|