@observertc/observer-js 1.0.0-beta.5 → 1.0.0-beta.6
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 +97 -48
- package/dist/index.d.mts +102 -126
- package/dist/index.d.ts +102 -126
- package/dist/index.js +188 -288
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +185 -288
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -104,9 +104,8 @@ import { Observer, ClientSample } from '@observertc/observer-js';
|
|
|
104
104
|
|
|
105
105
|
// 1. Create an observer.
|
|
106
106
|
const observer = new Observer({
|
|
107
|
-
//
|
|
108
|
-
updatePolicy: 'update-
|
|
109
|
-
updateIntervalInMs: 5000,
|
|
107
|
+
// when the observer aggregates call/client metrics:
|
|
108
|
+
updatePolicy: 'update-when-all-call-updated',
|
|
110
109
|
// default policy applied to calls created automatically by accept():
|
|
111
110
|
defaultCallUpdatePolicy: 'update-on-any-client-updated',
|
|
112
111
|
// optional auto-teardown:
|
|
@@ -177,7 +176,7 @@ client getStats() ──► ClientSample ──► observer.accept(sample, c
|
|
|
177
176
|
|-------|-----------|------------------------|-------|
|
|
178
177
|
| `Observer` | `new Observer(config?)` | — (root) | `observedCalls: Map<string, ObservedCall>`, global counters, the event bus |
|
|
179
178
|
| `ObservedCall` | `observer.createObservedCall(settings)` / lazily by `accept` | `observedCalls` | `observedClients: Map<string, ObservedClient>`, call-wide metrics, `detectors`, `scoreCalculator` |
|
|
180
|
-
| `ObservedClient` | `call.createObservedClient(settings)` / lazily | `observedClients` | `observedPeerConnections: Map<string, ObservedPeerConnection>`, per-client metrics
|
|
179
|
+
| `ObservedClient` | `call.createObservedClient(settings)` / lazily | `observedClients` | `observedPeerConnections: Map<string, ObservedPeerConnection>`, per-client metrics |
|
|
181
180
|
| `ObservedPeerConnection` | lazily, from `sample.peerConnections[]` | `observedPeerConnections` | the 15 sub-stat maps below, transport/RTT/bitrate metrics |
|
|
182
181
|
| Sub-stats | lazily, from the `PeerConnectionSample` | maps on the PC | individual WebRTC stat objects |
|
|
183
182
|
|
|
@@ -206,12 +205,49 @@ fields from the schema plus derived fields (deltas, bitrates).
|
|
|
206
205
|
|
|
207
206
|
The single entry point. It:
|
|
208
207
|
|
|
209
|
-
1. drops + emits `sample-rejected` if the observer is closed
|
|
210
|
-
2.
|
|
211
|
-
3.
|
|
212
|
-
4.
|
|
208
|
+
1. drops + emits `sample-rejected` if the observer is closed;
|
|
209
|
+
2. runs the sample through the **global accept-middleware chain** (see below);
|
|
210
|
+
3. (chain terminal) drops + emits `sample-rejected` if `callId`/`clientId` is missing;
|
|
211
|
+
4. gets or lazily creates the `ObservedCall` and `ObservedClient` (their `appData` comes from the
|
|
212
|
+
configured factories, never from `context`);
|
|
213
|
+
5. delegates to `client.accept(sample, context)`, which fans out to each
|
|
213
214
|
`ObservedPeerConnection.accept(pcSample, context)`.
|
|
214
215
|
|
|
216
|
+
### Accept middlewares (global pre-dispatch hook)
|
|
217
|
+
|
|
218
|
+
`observer.addAcceptMiddleware(...)` registers middlewares run on **every** sample inside
|
|
219
|
+
`accept()`, in order, **before** the sample is dispatched to any call or client. Each middleware
|
|
220
|
+
gets a `{ sample, context }` payload; it can inspect or mutate the sample (set/normalize
|
|
221
|
+
`callId`/`clientId`, enrich, redact) or the context, then call `next(payload)` to continue.
|
|
222
|
+
**Not calling `next` drops the sample** — nothing is created and no event fires. A throwing
|
|
223
|
+
middleware is caught and warns (the sample is dropped), never crashing `accept()`.
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { Observer, AcceptMiddleware } from '@observertc/observer-js';
|
|
227
|
+
|
|
228
|
+
const observer = new Observer();
|
|
229
|
+
|
|
230
|
+
// derive callId/clientId from the app's own attachment, before dispatch
|
|
231
|
+
const route: AcceptMiddleware = ({ sample }, next) => {
|
|
232
|
+
sample.callId ??= sample.attachments?.roomId as string;
|
|
233
|
+
sample.clientId ??= sample.attachments?.peerId as string;
|
|
234
|
+
next({ sample });
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// drop samples from a blocklisted client (never dispatched)
|
|
238
|
+
const filter: AcceptMiddleware = (payload, next) => {
|
|
239
|
+
if (blocked.has(payload.sample.clientId)) return; // no next() => dropped
|
|
240
|
+
next(payload);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
observer.addAcceptMiddleware(route, filter);
|
|
244
|
+
// observer.removeAcceptMiddleware(route);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
This is a lightweight global injection point, distinct from the larger (not-yet-built)
|
|
248
|
+
`ClientSampleProcessor` pipeline in the roadmap. When no middleware is registered, `accept()`
|
|
249
|
+
dispatches directly with no overhead.
|
|
250
|
+
|
|
215
251
|
### `context` (the `AcceptContext`)
|
|
216
252
|
|
|
217
253
|
```ts
|
|
@@ -259,9 +295,9 @@ These return `undefined` (and warn) when the parent is closed; `createObservedCa
|
|
|
259
295
|
## Update policies
|
|
260
296
|
|
|
261
297
|
"Update" means *recompute aggregated metrics and emit the `*-updated` event* at that level.
|
|
262
|
-
Both the observer and each call have a configurable trigger.
|
|
263
|
-
|
|
264
|
-
|
|
298
|
+
Both the observer and each call have a configurable trigger. Updates are **event-driven** — there
|
|
299
|
+
is no built-in timer. An app that wants a fixed cadence can call `observer.update()` /
|
|
300
|
+
`call.update()` from its own `setInterval`.
|
|
265
301
|
|
|
266
302
|
**Observer-level** (`ObserverConfig.updatePolicy`, default `update-when-all-call-updated`):
|
|
267
303
|
|
|
@@ -269,7 +305,6 @@ runtime a missing interval warns and falls back rather than throwing.
|
|
|
269
305
|
|--------|-------------------------------------|
|
|
270
306
|
| `update-on-any-call-updated` | any call updates |
|
|
271
307
|
| `update-when-all-call-updated` | every call has updated since the last observer update |
|
|
272
|
-
| `update-on-interval` | a timer fires (`updateIntervalInMs` required) |
|
|
273
308
|
|
|
274
309
|
**Call-level** (`ObservedCallSettings.updatePolicy`, defaulted from
|
|
275
310
|
`ObserverConfig.defaultCallUpdatePolicy`):
|
|
@@ -278,7 +313,6 @@ runtime a missing interval warns and falls back rather than throwing.
|
|
|
278
313
|
|--------|--------------------------------|
|
|
279
314
|
| `update-on-any-client-updated` | any client in the call updates |
|
|
280
315
|
| `update-when-all-client-updated` | every client has updated since the last call update |
|
|
281
|
-
| `update-on-interval` | a timer fires (`updateIntervalInMs` required) |
|
|
282
316
|
|
|
283
317
|
---
|
|
284
318
|
|
|
@@ -353,7 +387,6 @@ additional field(s) on top of that scope.
|
|
|
353
387
|
| `client-metadata` | `{ metaData: ClientMetaData }` | a client meta item arrived |
|
|
354
388
|
| `client-extension-stats` | `{ extensionStats: ExtensionStat }` | an app-defined extension stat arrived |
|
|
355
389
|
| `client-event` | `{ event: ClientEvent }` | any client event was processed |
|
|
356
|
-
| `client-track-report` | `{ report: TrackReport }` | a track was removed; final per-track report |
|
|
357
390
|
|
|
358
391
|
#### Peer-connection level — scope `{ observer, observedCall, observedClient, observedPeerConnection }`
|
|
359
392
|
|
|
@@ -403,12 +436,9 @@ listen to them, but prefer the bus equivalents above for application logic.
|
|
|
403
436
|
```ts
|
|
404
437
|
new Observer<AppData>(config?: ObserverConfig<AppData>)
|
|
405
438
|
|
|
406
|
-
type ObserverConfig<AppData = Record<string, unknown>> =
|
|
407
|
-
|
|
408
|
-
| { updatePolicy?: 'update-on-any-call-updated' | 'update-when-all-call-updated'; updateIntervalInMs?: number }
|
|
409
|
-
) & {
|
|
439
|
+
type ObserverConfig<AppData = Record<string, unknown>> = {
|
|
440
|
+
updatePolicy?: 'update-on-any-call-updated' | 'update-when-all-call-updated';
|
|
410
441
|
defaultCallUpdatePolicy?: ObservedCallSettings['updatePolicy'];
|
|
411
|
-
defaultCallUpdateIntervalInMs?: number;
|
|
412
442
|
appData?: AppData;
|
|
413
443
|
closeClientIfIdleForMs?: number;
|
|
414
444
|
closeCallIfEmptyForMs?: number;
|
|
@@ -418,6 +448,8 @@ type ObserverConfig<AppData = Record<string, unknown>> =
|
|
|
418
448
|
createClientAppData?: (p: { clientId: string; observedCall: ObservedCall }) => Record<string, unknown>;
|
|
419
449
|
// sink factory — produces a per-client sink that receives every accepted sample (see Sinks).
|
|
420
450
|
createClientSink?: (p: { clientId: string; observedCall: ObservedCall }) => ClientSampleSink | undefined;
|
|
451
|
+
// track-resolver factory — produces a call's RemoteTrackResolver (see Remote track resolution).
|
|
452
|
+
createTrackResolver?: (observedCall: ObservedCall) => RemoteTrackResolver | undefined;
|
|
421
453
|
};
|
|
422
454
|
```
|
|
423
455
|
|
|
@@ -438,6 +470,7 @@ const observer = new Observer({
|
|
|
438
470
|
Key members:
|
|
439
471
|
|
|
440
472
|
- `accept(sample: ClientSample, context?: AcceptContext): void`
|
|
473
|
+
- `addAcceptMiddleware(...mw: AcceptMiddleware[]): this` / `removeAcceptMiddleware(...mw): this` — global pre-dispatch sample hooks (see [Accept middlewares](#accept-middlewares-global-pre-dispatch-hook))
|
|
441
474
|
- `getObservedCall<T>(callId): ObservedCall<T> | undefined`
|
|
442
475
|
- `createObservedCall<T>(settings): ObservedCall<T> | undefined`
|
|
443
476
|
- `getOrCreateObservedCall<T>(settings): ObservedCall<T> | undefined`
|
|
@@ -454,13 +487,10 @@ Key members:
|
|
|
454
487
|
### `ObservedCall`
|
|
455
488
|
|
|
456
489
|
```ts
|
|
457
|
-
type ObservedCallSettings<AppData = Record<string, unknown>> =
|
|
458
|
-
|
|
459
|
-
| { updatePolicy?: 'update-on-any-client-updated' | 'update-when-all-client-updated'; updateIntervalInMs?: number }
|
|
460
|
-
) & {
|
|
490
|
+
type ObservedCallSettings<AppData = Record<string, unknown>> = {
|
|
491
|
+
updatePolicy?: 'update-on-any-client-updated' | 'update-when-all-client-updated';
|
|
461
492
|
callId: string;
|
|
462
493
|
appData?: AppData;
|
|
463
|
-
remoteTrackResolvePolicy?: 'p2p' | 'mediasoup-sfu' | 'none';
|
|
464
494
|
closeCallIfEmptyForMs?: number;
|
|
465
495
|
};
|
|
466
496
|
```
|
|
@@ -473,7 +503,7 @@ Key members:
|
|
|
473
503
|
- `addIssue(issue: ClientIssue): void` — raise a **call-level** issue → emits `call-issue`
|
|
474
504
|
- `readonly detectors: Detectors` — server-side detector registry (empty by default; see [Detectors](#detectors-server-side-extension-point))
|
|
475
505
|
- `scoreCalculator: ScoreCalculator`, `get score()`, `readonly calculatedScore`
|
|
476
|
-
- `remoteTrackResolver?: RemoteTrackResolver`
|
|
506
|
+
- `remoteTrackResolver?: RemoteTrackResolver` — set from `ObserverConfig.createTrackResolver` at call creation (see [Remote track resolution](#remote-track-resolution-mediasoup--sfu))
|
|
477
507
|
- aggregates: `numberOfIssues`, `numberOfPeerConnections`, `numberOfInboundRtpStreams`,
|
|
478
508
|
`numberOfOutboundRtpStreams`, `numberOfDataChannels`, `maxNumberOfClients`,
|
|
479
509
|
`clientsUsedTurn: Set<string>`, `startedAt?`, `endedAt?`, `closedAt?`, `closed`
|
|
@@ -507,7 +537,6 @@ Key members:
|
|
|
507
537
|
- Per-tick deltas: `deltaReceivedAudioBytes`, `deltaSentAudioBytes`, … (see source for the full set)
|
|
508
538
|
- Lifecycle: `joinedAt?`, `leftAt?`, `closedAt?`, `closed`, `get score()`
|
|
509
539
|
- Metadata: `browser?`, `engine?`, `platform?`, `operationSystem?`, `mediaDevices`, `mediaConstraints`
|
|
510
|
-
- `readonly report: ClientReport` — cumulative report (byte/packet totals + RTT & score distributions)
|
|
511
540
|
- `accept(sample, context?)`, `close()`
|
|
512
541
|
|
|
513
542
|
### `ObservedPeerConnection`
|
|
@@ -588,9 +617,6 @@ mediasoup set `PRODUCER_*` / `CONSUMER_*` / `DATA_PRODUCER_*` / `DATA_CONSUMER_*
|
|
|
588
617
|
`MEDIA_DEVICES_SUPPORTED_CONSTRAINTS`, `USER_MEDIA_ERROR`, `LOCAL_SDP`, `OPERATION_SYSTEM`,
|
|
589
618
|
`ENGINE`, `PLATFORM`, `BROWSER`.
|
|
590
619
|
|
|
591
|
-
`Reports` exported: `ClientReport` (cumulative per-client totals + RTT/score distributions) and
|
|
592
|
-
`TrackReport` (per-track final report, delivered on `client-track-report`).
|
|
593
|
-
|
|
594
620
|
---
|
|
595
621
|
|
|
596
622
|
## Detectors (server-side extension point)
|
|
@@ -643,29 +669,47 @@ class Detectors {
|
|
|
643
669
|
## Remote track resolution (mediasoup / SFU)
|
|
644
670
|
|
|
645
671
|
In an SFU, one participant's **outbound** track is delivered to other participants as **inbound**
|
|
646
|
-
tracks
|
|
647
|
-
|
|
648
|
-
|
|
672
|
+
tracks (one **publisher** → many **subscribers**). Correlation is **opt-in** per observer: set
|
|
673
|
+
`ObserverConfig.createTrackResolver`, a factory invoked when each call is created that returns the
|
|
674
|
+
call's `RemoteTrackResolver` (or `undefined` for none).
|
|
675
|
+
|
|
676
|
+
`RemoteTrackResolver` is a generic, strategy-driven class. It subscribes to the bus (filtered to
|
|
677
|
+
its call) and links tracks by **publisher id** — the link key — maintaining the links directly on
|
|
678
|
+
the tracks: `inboundTrack.remoteOutboundTrack` and `outboundTrack.remoteInboundTracks: Set`.
|
|
649
679
|
|
|
650
680
|
```ts
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
681
|
+
import { Observer, createDefaultMediasoupRemoteTrackResolverFactory } from '@observertc/observer-js';
|
|
682
|
+
|
|
683
|
+
const observer = new Observer({
|
|
684
|
+
createTrackResolver: createDefaultMediasoupRemoteTrackResolverFactory(),
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// later, given tracks (links are kept up to date as tracks come and go):
|
|
688
|
+
const source = inboundTrack.remoteOutboundTrack; // the publishing ObservedOutboundTrack
|
|
689
|
+
const receivers = [ ...outboundTrack.remoteInboundTracks ]; // the subscribing ObservedInboundTrack[]
|
|
655
690
|
```
|
|
656
691
|
|
|
657
|
-
|
|
658
|
-
`
|
|
692
|
+
Two built-in factories ship: `createDefaultMediasoupRemoteTrackResolverFactory()` (publisher =
|
|
693
|
+
`attachments.producerId`, subscriber = `attachments.consumerId`) and
|
|
694
|
+
`createP2pRemoteTrackResolverFactory()` (matches by RTP **SSRC**, preserved end-to-end in p2p).
|
|
695
|
+
|
|
696
|
+
For any other topology, build a `RemoteTrackResolver` with your own key resolvers — the publisher
|
|
697
|
+
id is just whatever links a subscribed track to the published one:
|
|
659
698
|
|
|
660
699
|
```ts
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
700
|
+
import { Observer, RemoteTrackResolver } from '@observertc/observer-js';
|
|
701
|
+
|
|
702
|
+
const observer = new Observer({
|
|
703
|
+
createTrackResolver: (observedCall) => new RemoteTrackResolver(observedCall, {
|
|
704
|
+
resolveOutboundTrackPublisherId: (out) => out.attachments?.mediaId as string | undefined,
|
|
705
|
+
resolveInboundTrackPublisherId: (inb) => inb.attachments?.mediaId as string | undefined,
|
|
706
|
+
resolveInboundTrackSubscriberId: (inb) => inb.attachments?.subId as string | undefined, // optional
|
|
707
|
+
}),
|
|
708
|
+
});
|
|
665
709
|
```
|
|
666
710
|
|
|
667
|
-
|
|
668
|
-
|
|
711
|
+
For the mediasoup factory, the application puts `producerId` / `consumerId` (and optionally
|
|
712
|
+
`direction`, `label`) into the track `attachments`.
|
|
669
713
|
|
|
670
714
|
---
|
|
671
715
|
|
|
@@ -819,14 +863,15 @@ filtering, per-module routing, and full silencing.
|
|
|
819
863
|
|
|
820
864
|
The library **warns and degrades; it does not throw** on operational problems:
|
|
821
865
|
|
|
822
|
-
- Bad config (`update-on-interval` without an interval) → warn + safe fallback policy.
|
|
823
866
|
- `createObservedCall` / `createObservedClient` on a closed parent → warn + return `undefined`.
|
|
824
867
|
- Duplicate id → warn + return the **existing** instance.
|
|
825
868
|
- `accept()` on a closed client → warn + no-op.
|
|
826
869
|
- Sample missing `callId`/`clientId`, or observer closed → `sample-rejected` event.
|
|
870
|
+
- A throwing accept-middleware → warn + drop that sample (never crashes `accept()`).
|
|
827
871
|
|
|
828
|
-
Therefore `create*` and `getOrCreate*` return `T | undefined`; **guard the result.** The
|
|
829
|
-
|
|
872
|
+
Therefore `create*` and `getOrCreate*` return `T | undefined`; **guard the result.** The
|
|
873
|
+
`Middleware` utility's internal invariants (e.g. calling `next()` twice) throw, but those throws
|
|
874
|
+
are caught by `accept()` and surfaced as a warning.
|
|
830
875
|
|
|
831
876
|
---
|
|
832
877
|
|
|
@@ -863,7 +908,11 @@ export type { JsonlFileSinkOptions, JsonlFileSinkFactoryOptions } from './sinks/
|
|
|
863
908
|
export { InMemorySink, createInMemorySink } from './sinks/InMemorySink';
|
|
864
909
|
|
|
865
910
|
export { Middleware } from './common/Middleware';
|
|
866
|
-
|
|
911
|
+
|
|
912
|
+
// remote track correlation
|
|
913
|
+
export { RemoteTrackResolver } from './utils/RemoteTrackResolver';
|
|
914
|
+
export type { RemoteTrackResolvers, RemoteTrackResolverFactory } from './utils/RemoteTrackResolver';
|
|
915
|
+
export { createDefaultMediasoupRemoteTrackResolverFactory, createP2pRemoteTrackResolverFactory } from './utils/RemoteTrackResolverFactories';
|
|
867
916
|
```
|
|
868
917
|
|
|
869
918
|
---
|
package/dist/index.d.mts
CHANGED
|
@@ -1340,6 +1340,25 @@ type CalculatedScore = {
|
|
|
1340
1340
|
|
|
1341
1341
|
type MediaKind = 'audio' | 'video';
|
|
1342
1342
|
|
|
1343
|
+
declare class ObservedMediaPlayout implements MediaPlayoutStats {
|
|
1344
|
+
timestamp: number;
|
|
1345
|
+
id: string;
|
|
1346
|
+
kind: MediaKind;
|
|
1347
|
+
private readonly _peerConnection;
|
|
1348
|
+
private _visited;
|
|
1349
|
+
appData?: Record<string, unknown>;
|
|
1350
|
+
synthesizedSamplesDuration?: number | undefined;
|
|
1351
|
+
synthesizedSamplesEvents?: number | undefined;
|
|
1352
|
+
totalSamplesDuration?: number | undefined;
|
|
1353
|
+
totalPlayoutDelay?: number | undefined;
|
|
1354
|
+
totalSamplesCount?: number | undefined;
|
|
1355
|
+
attachments?: Record<string, unknown> | undefined;
|
|
1356
|
+
constructor(timestamp: number, id: string, kind: MediaKind, _peerConnection: ObservedPeerConnection);
|
|
1357
|
+
get visited(): boolean;
|
|
1358
|
+
getPeerConnection(): ObservedPeerConnection;
|
|
1359
|
+
update(stats: MediaPlayoutStats): void;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1343
1362
|
declare class ObservedMediaSource implements MediaSourceStats {
|
|
1344
1363
|
timestamp: number;
|
|
1345
1364
|
id: string;
|
|
@@ -1579,91 +1598,6 @@ declare class ObservedOutboundRtp implements OutboundRtpStats {
|
|
|
1579
1598
|
update(stats: OutboundRtpStats): void;
|
|
1580
1599
|
}
|
|
1581
1600
|
|
|
1582
|
-
type InboundAudioTrackReport = {
|
|
1583
|
-
trackId: string;
|
|
1584
|
-
fractionLostDistribution: {
|
|
1585
|
-
lt001: number;
|
|
1586
|
-
lt005: number;
|
|
1587
|
-
lt010: number;
|
|
1588
|
-
lt020: number;
|
|
1589
|
-
lt050: number;
|
|
1590
|
-
gtOrEq050: number;
|
|
1591
|
-
count: number;
|
|
1592
|
-
sum: number;
|
|
1593
|
-
};
|
|
1594
|
-
};
|
|
1595
|
-
type InboundVideoTrackReport = {
|
|
1596
|
-
trackId: string;
|
|
1597
|
-
fractionLostDistribution: {
|
|
1598
|
-
lt001: number;
|
|
1599
|
-
lt005: number;
|
|
1600
|
-
lt010: number;
|
|
1601
|
-
lt020: number;
|
|
1602
|
-
lt050: number;
|
|
1603
|
-
gtOrEq050: number;
|
|
1604
|
-
count: number;
|
|
1605
|
-
sum: number;
|
|
1606
|
-
};
|
|
1607
|
-
};
|
|
1608
|
-
type OutboundAudioTrackReport = {
|
|
1609
|
-
trackId: string;
|
|
1610
|
-
};
|
|
1611
|
-
type OutboundVideoTrackReport = {
|
|
1612
|
-
trackId: string;
|
|
1613
|
-
};
|
|
1614
|
-
type InboundTrackReport = ({
|
|
1615
|
-
kind: 'audio';
|
|
1616
|
-
} & InboundAudioTrackReport) | ({
|
|
1617
|
-
kind: 'video';
|
|
1618
|
-
} & InboundVideoTrackReport);
|
|
1619
|
-
type OutboundTrackReport = ({
|
|
1620
|
-
kind: 'audio';
|
|
1621
|
-
} & OutboundAudioTrackReport) | ({
|
|
1622
|
-
kind: 'video';
|
|
1623
|
-
} & OutboundVideoTrackReport);
|
|
1624
|
-
type TrackReport = ({
|
|
1625
|
-
direction: 'inbound';
|
|
1626
|
-
} & InboundTrackReport) | ({
|
|
1627
|
-
direction: 'outbound';
|
|
1628
|
-
} & OutboundTrackReport);
|
|
1629
|
-
type ClientReport = {
|
|
1630
|
-
callId: string;
|
|
1631
|
-
clientId: string;
|
|
1632
|
-
totalDataChannelBytesReceived: number;
|
|
1633
|
-
totalDataChannelBytesSent: number;
|
|
1634
|
-
totalDataChannelMessagesReceived: number;
|
|
1635
|
-
totalDataChannelMessagesSent: number;
|
|
1636
|
-
totalInboundRtpPacketsReceived: number;
|
|
1637
|
-
totalInboundRtpPacketsLost: number;
|
|
1638
|
-
totalInboundRtpBytesReceived: number;
|
|
1639
|
-
totalOutboundRtpPacketsSent: number;
|
|
1640
|
-
totalOutboundRtpBytesSent: number;
|
|
1641
|
-
totalAudioBytesReceived: number;
|
|
1642
|
-
totalVideoBytesReceived: number;
|
|
1643
|
-
totalAudioBytesSent: number;
|
|
1644
|
-
totalVideoBytesSent: number;
|
|
1645
|
-
totalNumberOfIssues: number;
|
|
1646
|
-
issues: Record<string, number>;
|
|
1647
|
-
rttDistribution: {
|
|
1648
|
-
lt50ms: number;
|
|
1649
|
-
lt150ms: number;
|
|
1650
|
-
lt300ms: number;
|
|
1651
|
-
gtOrEq300ms: number;
|
|
1652
|
-
count: number;
|
|
1653
|
-
sum: number;
|
|
1654
|
-
};
|
|
1655
|
-
scoreDistribution: {
|
|
1656
|
-
'0': number;
|
|
1657
|
-
'1': number;
|
|
1658
|
-
'2': number;
|
|
1659
|
-
'3': number;
|
|
1660
|
-
'4': number;
|
|
1661
|
-
'5': number;
|
|
1662
|
-
count: number;
|
|
1663
|
-
sum: number;
|
|
1664
|
-
};
|
|
1665
|
-
};
|
|
1666
|
-
|
|
1667
1601
|
declare class ObservedOutboundTrack implements OutboundTrackSample {
|
|
1668
1602
|
timestamp: number;
|
|
1669
1603
|
readonly id: string;
|
|
@@ -1673,7 +1607,7 @@ declare class ObservedOutboundTrack implements OutboundTrackSample {
|
|
|
1673
1607
|
private readonly _mediaSource?;
|
|
1674
1608
|
private _visited;
|
|
1675
1609
|
appData?: Record<string, unknown>;
|
|
1676
|
-
readonly
|
|
1610
|
+
readonly remoteInboundTracks: Set<ObservedInboundTrack>;
|
|
1677
1611
|
readonly calculatedScore: CalculatedScore;
|
|
1678
1612
|
addedAt?: number | undefined;
|
|
1679
1613
|
removedAt?: number | undefined;
|
|
@@ -1685,29 +1619,9 @@ declare class ObservedOutboundTrack implements OutboundTrackSample {
|
|
|
1685
1619
|
getPeerConnection(): ObservedPeerConnection;
|
|
1686
1620
|
getOutboundRtps(): ObservedOutboundRtp[] | undefined;
|
|
1687
1621
|
getMediaSource(): ObservedMediaSource | undefined;
|
|
1688
|
-
getRemoteInboundTracks(): ObservedInboundTrack[] | undefined;
|
|
1689
1622
|
update(stats: OutboundTrackSample): void;
|
|
1690
1623
|
}
|
|
1691
1624
|
|
|
1692
|
-
declare class ObservedMediaPlayout implements MediaPlayoutStats {
|
|
1693
|
-
timestamp: number;
|
|
1694
|
-
id: string;
|
|
1695
|
-
kind: MediaKind;
|
|
1696
|
-
private readonly _peerConnection;
|
|
1697
|
-
private _visited;
|
|
1698
|
-
appData?: Record<string, unknown>;
|
|
1699
|
-
synthesizedSamplesDuration?: number | undefined;
|
|
1700
|
-
synthesizedSamplesEvents?: number | undefined;
|
|
1701
|
-
totalSamplesDuration?: number | undefined;
|
|
1702
|
-
totalPlayoutDelay?: number | undefined;
|
|
1703
|
-
totalSamplesCount?: number | undefined;
|
|
1704
|
-
attachments?: Record<string, unknown> | undefined;
|
|
1705
|
-
constructor(timestamp: number, id: string, kind: MediaKind, _peerConnection: ObservedPeerConnection);
|
|
1706
|
-
get visited(): boolean;
|
|
1707
|
-
getPeerConnection(): ObservedPeerConnection;
|
|
1708
|
-
update(stats: MediaPlayoutStats): void;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
1625
|
declare class ObservedInboundTrack implements InboundTrackSample {
|
|
1712
1626
|
timestamp: number;
|
|
1713
1627
|
readonly id: string;
|
|
@@ -1717,7 +1631,7 @@ declare class ObservedInboundTrack implements InboundTrackSample {
|
|
|
1717
1631
|
private readonly _mediaPlayout?;
|
|
1718
1632
|
readonly calculatedScore: CalculatedScore;
|
|
1719
1633
|
appData?: Record<string, unknown>;
|
|
1720
|
-
|
|
1634
|
+
remoteOutboundTrack?: ObservedOutboundTrack | undefined;
|
|
1721
1635
|
private _visited;
|
|
1722
1636
|
addedAt?: number | undefined;
|
|
1723
1637
|
removedAt?: number | undefined;
|
|
@@ -1729,7 +1643,6 @@ declare class ObservedInboundTrack implements InboundTrackSample {
|
|
|
1729
1643
|
getPeerConnection(): ObservedPeerConnection;
|
|
1730
1644
|
getInboundRtp(): ObservedInboundRtp | undefined;
|
|
1731
1645
|
getMediaPlayout(): ObservedMediaPlayout | undefined;
|
|
1732
|
-
getRemoteOutboundTrack(): ObservedOutboundTrack | undefined;
|
|
1733
1646
|
update(stats: InboundTrackSample): void;
|
|
1734
1647
|
}
|
|
1735
1648
|
|
|
@@ -2237,9 +2150,6 @@ type ObserverEvents = {
|
|
|
2237
2150
|
'client-event': [ObservedClientScope & {
|
|
2238
2151
|
event: ClientEvent;
|
|
2239
2152
|
}];
|
|
2240
|
-
'client-track-report': [ObservedClientScope & {
|
|
2241
|
-
report: TrackReport;
|
|
2242
|
-
}];
|
|
2243
2153
|
'peer-connection-added': [ObservedPeerConnectionScope];
|
|
2244
2154
|
'peer-connection-updated': [ObservedPeerConnectionScope];
|
|
2245
2155
|
'peer-connection-closed': [ObservedPeerConnectionScope];
|
|
@@ -2479,7 +2389,6 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
2479
2389
|
deltaNumberOfIssues: number;
|
|
2480
2390
|
totalScoreSum: number;
|
|
2481
2391
|
numberOfScoreMeasurements: number;
|
|
2482
|
-
readonly report: ClientReport;
|
|
2483
2392
|
readonly mediaDevices: MediaDeviceInfo[];
|
|
2484
2393
|
issues: ClientIssue[];
|
|
2485
2394
|
private _injections;
|
|
@@ -2508,9 +2417,49 @@ interface ScoreCalculator {
|
|
|
2508
2417
|
update(): void;
|
|
2509
2418
|
}
|
|
2510
2419
|
|
|
2511
|
-
|
|
2420
|
+
type RemoteTrackResolverFactory = (observedCall: ObservedCall) => RemoteTrackResolver;
|
|
2421
|
+
/**
|
|
2422
|
+
* Strategy functions that map a track to its publish/subscribe identity. Two tracks are linked
|
|
2423
|
+
* when a subscribed (inbound) track's **publisher id** equals a published (outbound) track's
|
|
2424
|
+
* **publisher id** — one publisher → many subscribers. The **subscriber id** is optional and is
|
|
2425
|
+
* only used to look an inbound track up via `getInboundTrackBySubscriberId`.
|
|
2426
|
+
*
|
|
2427
|
+
* The "publisher id" is whatever a strategy uses as the link key: mediasoup passes its producerId,
|
|
2428
|
+
* a p2p strategy the SSRC, a generic strategy a shared attachment value.
|
|
2429
|
+
*/
|
|
2430
|
+
type RemoteTrackResolvers = {
|
|
2431
|
+
/** The publisher id a subscribed (inbound) track receives. Required — this is the link key. */
|
|
2432
|
+
resolveInboundTrackPublisherId: (inboundTrack: ObservedInboundTrack) => string | undefined;
|
|
2433
|
+
/** The publisher id a published (outbound) track represents. Required — this is the link key. */
|
|
2434
|
+
resolveOutboundTrackPublisherId: (outboundTrack: ObservedOutboundTrack) => string | undefined;
|
|
2435
|
+
/** Optional: the inbound track's own subscription id (enables `getInboundTrackBySubscriberId`). */
|
|
2436
|
+
resolveInboundTrackSubscriberId?: (inboundTrack: ObservedInboundTrack) => string | undefined;
|
|
2437
|
+
};
|
|
2438
|
+
/**
|
|
2439
|
+
* Generic, strategy-driven base resolver. It subscribes to the Observer bus (filtered to one call),
|
|
2440
|
+
* links subscribed↔published tracks by publisher id, and maintains the links directly on the tracks
|
|
2441
|
+
* (`inboundTrack.remoteOutboundTrack`, `outboundTrack.remoteInboundTracks`).
|
|
2442
|
+
*
|
|
2443
|
+
* Concrete strategies (mediasoup, p2p-by-SSRC, generic attachment) are just different
|
|
2444
|
+
* {@link RemoteTrackResolvers} passed to this class. Ids are re-resolved on demand rather than
|
|
2445
|
+
* cached, so the only state kept is the two lookup indexes that back the public getters.
|
|
2446
|
+
*/
|
|
2447
|
+
declare class RemoteTrackResolver {
|
|
2448
|
+
readonly observedCall: ObservedCall;
|
|
2449
|
+
private readonly resolvers;
|
|
2450
|
+
private readonly _publisherIdToOutboundTrack;
|
|
2451
|
+
private readonly _subscriberIdToInboundTrack;
|
|
2452
|
+
constructor(observedCall: ObservedCall, resolvers: RemoteTrackResolvers);
|
|
2453
|
+
/** The published (outbound) track for a publisher id, if any. */
|
|
2454
|
+
getOutboundTrackByPublisherId(publisherId: string): ObservedOutboundTrack | undefined;
|
|
2455
|
+
/** The subscribed (inbound) track for a subscriber id, if the strategy resolves subscriber ids. */
|
|
2456
|
+
getInboundTrackBySubscriberId(subscriberId: string): ObservedInboundTrack | undefined;
|
|
2512
2457
|
resolveRemoteOutboundTrack(inboundTrack: ObservedInboundTrack): ObservedOutboundTrack | undefined;
|
|
2513
2458
|
resolveRemoteInboundTracks(outboundTrack: ObservedOutboundTrack): ObservedInboundTrack[] | undefined;
|
|
2459
|
+
private _addInboundTrack;
|
|
2460
|
+
private _removeInboundTrack;
|
|
2461
|
+
private _addOutboundTrack;
|
|
2462
|
+
private _removeOutboundTrack;
|
|
2514
2463
|
}
|
|
2515
2464
|
|
|
2516
2465
|
interface Updater {
|
|
@@ -2536,16 +2485,11 @@ declare class Detectors {
|
|
|
2536
2485
|
}
|
|
2537
2486
|
|
|
2538
2487
|
type ObservedCallUpdateConfig = {
|
|
2539
|
-
updatePolicy: 'update-on-interval';
|
|
2540
|
-
updateIntervalInMs: number;
|
|
2541
|
-
} | {
|
|
2542
2488
|
updatePolicy?: 'update-on-any-client-updated' | 'update-when-all-client-updated';
|
|
2543
|
-
updateIntervalInMs?: number;
|
|
2544
2489
|
};
|
|
2545
2490
|
type ObservedCallSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = ObservedCallUpdateConfig & {
|
|
2546
2491
|
callId: string;
|
|
2547
2492
|
appData?: AppData;
|
|
2548
|
-
remoteTrackResolvePolicy?: 'p2p' | 'mediasoup-sfu' | 'none';
|
|
2549
2493
|
closeCallIfEmptyForMs?: number;
|
|
2550
2494
|
};
|
|
2551
2495
|
type ObservedCallEvents = {
|
|
@@ -2606,6 +2550,22 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
|
|
|
2606
2550
|
private _notify;
|
|
2607
2551
|
}
|
|
2608
2552
|
|
|
2553
|
+
type Middleware<T> = (input: T, next: (nextInput: T) => void) => void;
|
|
2554
|
+
interface Processor<T> {
|
|
2555
|
+
finalCallback?: Callback<T>;
|
|
2556
|
+
process(value: T): void;
|
|
2557
|
+
addMiddleware(...middlewares: Middleware<T>[]): Processor<T>;
|
|
2558
|
+
removeMiddleware(...middlewares: Middleware<T>[]): Processor<T>;
|
|
2559
|
+
}
|
|
2560
|
+
type Callback<T> = (input: T) => void;
|
|
2561
|
+
declare class MiddlewareProcessor<T> implements Processor<T> {
|
|
2562
|
+
private stack;
|
|
2563
|
+
finalCallback?: Callback<T>;
|
|
2564
|
+
addMiddleware(...middlewares: Middleware<T>[]): Processor<T>;
|
|
2565
|
+
removeMiddleware(...middlewares: Middleware<T>[]): Processor<T>;
|
|
2566
|
+
process(value: T): void;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2609
2569
|
type SampleRejectedReason = 'observer-closed' | 'missing-callId' | 'missing-clientId';
|
|
2610
2570
|
|
|
2611
2571
|
/**
|
|
@@ -2614,12 +2574,20 @@ type SampleRejectedReason = 'observer-closed' | 'missing-callId' | 'missing-clie
|
|
|
2614
2574
|
* merged into the `appData` of entities created during the accept pass.
|
|
2615
2575
|
*/
|
|
2616
2576
|
type AcceptContext = Record<string, unknown>;
|
|
2577
|
+
/** The payload threaded through `accept()` middlewares: the sample and its optional context. */
|
|
2578
|
+
type AcceptMiddlewarePayload = {
|
|
2579
|
+
sample: ClientSample;
|
|
2580
|
+
context?: AcceptContext;
|
|
2581
|
+
};
|
|
2582
|
+
/**
|
|
2583
|
+
* A global middleware run on every sample passed to `observer.accept()`, in registration order,
|
|
2584
|
+
* **before** the sample is dispatched to any call/client. It may inspect or mutate the sample
|
|
2585
|
+
* (e.g. set/normalize `callId`/`clientId`, enrich, redact) or the context, then call
|
|
2586
|
+
* `next(payload)` to continue the chain. Not calling `next` **drops** the sample.
|
|
2587
|
+
*/
|
|
2588
|
+
type AcceptMiddleware = Middleware<AcceptMiddlewarePayload>;
|
|
2617
2589
|
type ObserverUpdateConfig = {
|
|
2618
|
-
updatePolicy: 'update-on-interval';
|
|
2619
|
-
updateIntervalInMs: number;
|
|
2620
|
-
} | {
|
|
2621
2590
|
updatePolicy?: 'update-on-any-call-updated' | 'update-when-all-call-updated';
|
|
2622
|
-
updateIntervalInMs?: number;
|
|
2623
2591
|
};
|
|
2624
2592
|
/** Produces the initial `appData` for a call created without an explicit `appData`. */
|
|
2625
2593
|
type CallAppDataFactory = (params: {
|
|
@@ -2633,7 +2601,6 @@ type ClientAppDataFactory = (params: {
|
|
|
2633
2601
|
}) => Record<string, unknown>;
|
|
2634
2602
|
type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unknown>> = ObserverUpdateConfig & {
|
|
2635
2603
|
defaultCallUpdatePolicy?: ObservedCallSettings['updatePolicy'];
|
|
2636
|
-
defaultCallUpdateIntervalInMs?: number;
|
|
2637
2604
|
appData?: AppData;
|
|
2638
2605
|
closeClientIfIdleForMs?: number;
|
|
2639
2606
|
closeCallIfEmptyForMs?: number;
|
|
@@ -2651,6 +2618,13 @@ type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unk
|
|
|
2651
2618
|
* can be derived from `callId` / `clientId`.
|
|
2652
2619
|
*/
|
|
2653
2620
|
createClientSink?: ClientSampleSinkFactory;
|
|
2621
|
+
/**
|
|
2622
|
+
* Optional factory invoked when a call is created, producing the call's `RemoteTrackResolver`
|
|
2623
|
+
* (or `undefined` for none). Use the built-ins
|
|
2624
|
+
* (`createDefaultMediasoupRemoteTrackResolverFactory()` / `createP2pRemoteTrackResolverFactory()`)
|
|
2625
|
+
* or build a `RemoteTrackResolver` with custom publisher/subscriber id resolvers.
|
|
2626
|
+
*/
|
|
2627
|
+
createTrackResolver?: RemoteTrackResolverFactory;
|
|
2654
2628
|
};
|
|
2655
2629
|
declare interface Observer {
|
|
2656
2630
|
on<U extends keyof ObserverEvents>(event: U, listener: (...args: ObserverEvents[U]) => void): this;
|
|
@@ -2674,7 +2648,8 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
2674
2648
|
numberOfOutboundRtpStreams: number;
|
|
2675
2649
|
numberOfDataChannels: number;
|
|
2676
2650
|
numberOfPeerConnections: number;
|
|
2677
|
-
|
|
2651
|
+
/** Global, pre-dispatch middleware chain run on every accepted sample. */
|
|
2652
|
+
readonly acceptMiddlewares: MiddlewareProcessor<AcceptMiddlewarePayload>;
|
|
2678
2653
|
constructor(config?: ObserverConfig<AppData>);
|
|
2679
2654
|
get numberOfCalls(): number;
|
|
2680
2655
|
get appData(): AppData | undefined;
|
|
@@ -2790,6 +2765,7 @@ declare class InMemorySink extends ClientSampleSink {
|
|
|
2790
2765
|
}
|
|
2791
2766
|
declare function createInMemorySink(samples?: ClientSample[]): InMemorySink;
|
|
2792
2767
|
|
|
2793
|
-
|
|
2768
|
+
declare function createDefaultMediasoupRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
2769
|
+
declare function createP2pRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
2794
2770
|
|
|
2795
|
-
export { type AcceptContext, type CallAppDataFactory, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientIssue, type ClientMetaData, ClientMetaTypes, type
|
|
2771
|
+
export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type CallAppDataFactory, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientIssue, type ClientMetaData, ClientMetaTypes, type ClientSample, ClientSampleSink, type ClientSampleSinkEvents, type ClientSampleSinkFactory, type Detector, Detectors, InMemorySink, JsonlFileSink, type JsonlFileSinkFactoryOptions, type JsonlFileSinkOptions, type Logger, type Middleware, ObservedCall, type ObservedCallScope, ObservedCertificate, ObservedClient, type ObservedClientScope, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, Observer, type ObserverEventBase, type ObserverEvents, type ObserverLogger, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolvers, type SampleRejectedReason, type ScoreCalculator, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createJsonlFileSinkFactory, createLogger, createP2pRemoteTrackResolverFactory, setObserverLogger };
|