@observertc/observer-js 1.0.0-beta.8 → 1.0.0-beta.9
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 +240 -99
- package/dist/index.d.mts +186 -1
- package/dist/index.d.ts +186 -1
- package/dist/index.js +393 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +392 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@observertc/observer-js)
|
|
4
4
|
[](https://github.com/observertc/observer-js/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
+
> **In one line:** feed it WebRTC `getStats()` snapshots, and get back a live, queryable model of
|
|
7
|
+
> every call plus a single typed event stream to react to.
|
|
8
|
+
|
|
6
9
|
`observer-js` is a **server-side Node.js library for monitoring WebRTC sessions**. A WebRTC
|
|
7
10
|
application (typically an SFU or a signaling/stats backend) feeds it `ClientSample` objects —
|
|
8
11
|
periodic snapshots of each participant's `RTCPeerConnection.getStats()` output plus
|
|
@@ -10,6 +13,24 @@ application events — and `observer-js` maintains a live, in-memory model of ev
|
|
|
10
13
|
participant, peer connection, and media stream, derives per-interval and cumulative metrics,
|
|
11
14
|
and emits a single, unified stream of typed events the application can react to.
|
|
12
15
|
|
|
16
|
+
**What you can do with it:**
|
|
17
|
+
|
|
18
|
+
- **Monitor calls live** — a queryable in-memory tree of every call, client, peer connection,
|
|
19
|
+
track, codec, ICE candidate and data channel, each holding current **and** cumulative metrics.
|
|
20
|
+
- **React on one event bus** — subscribe once on the `Observer`; every payload carries its full
|
|
21
|
+
ancestry (`call → client → peer connection → stat`), so you never walk the tree to subscribe.
|
|
22
|
+
- **Get derived metrics for free** — counter-reset-safe per-tick deltas, bitrates, jitter, RTT,
|
|
23
|
+
fraction-lost, remote-RTP (RTCP) correlation, and TURN/TCP usage from the selected candidate pair.
|
|
24
|
+
- **Correlate across an SFU** — link a publisher's outbound track to every subscriber's inbound
|
|
25
|
+
track (`RemoteTrackResolver`), and observe mediasoup routers/transports/producers/consumers
|
|
26
|
+
on the server side.
|
|
27
|
+
- **Detect server-only problems** — cross-client `Detector`s raise `call-issue`s for conditions no
|
|
28
|
+
single client can see (e.g. everyone in a call degrading at once).
|
|
29
|
+
- **Persist every sample** — per-client sinks (JSONL file, in-memory, or your own) for archival,
|
|
30
|
+
streaming, and offline replay.
|
|
31
|
+
- **Drop it in safely** — warn-don't-throw, a pluggable logger, dual **ESM + CommonJS**, and **no**
|
|
32
|
+
media-stack dependency in the core.
|
|
33
|
+
|
|
13
34
|
> **Status:** `1.0.0-beta`. The API described here is current and intended to be implemented
|
|
14
35
|
> against directly. This document is written to be self-sufficient: an engineer (or an AI
|
|
15
36
|
> agent) should be able to integrate the library, or develop it further, from this file alone.
|
|
@@ -24,23 +45,21 @@ and emits a single, unified stream of typed events the application can react to.
|
|
|
24
45
|
## Table of contents
|
|
25
46
|
|
|
26
47
|
1. [Installation](#installation)
|
|
27
|
-
2. [
|
|
28
|
-
3. [
|
|
29
|
-
4. [
|
|
30
|
-
5. [
|
|
31
|
-
6. [
|
|
32
|
-
7. [
|
|
33
|
-
8. [
|
|
34
|
-
9. [
|
|
35
|
-
10. [
|
|
36
|
-
11. [
|
|
37
|
-
12. [
|
|
48
|
+
2. [Quick start](#quick-start)
|
|
49
|
+
3. [Data flow](#data-flow)
|
|
50
|
+
4. [Entity hierarchy](#entity-hierarchy)
|
|
51
|
+
5. [Ingestion: `accept()`, context & lifecycle](#ingestion-accept-context--lifecycle)
|
|
52
|
+
6. [Update policies](#update-policies)
|
|
53
|
+
7. [The event bus](#the-event-bus) ← the core of the API
|
|
54
|
+
8. [API reference](#api-reference)
|
|
55
|
+
9. [Schema types (`ClientSample`)](#schema-types-clientsample)
|
|
56
|
+
10. [Detectors (server-side extension point)](#detectors-server-side-extension-point)
|
|
57
|
+
11. [Remote track resolution (mediasoup / SFU)](#remote-track-resolution-mediasoup--sfu)
|
|
58
|
+
12. [Mediasoup router observation](#mediasoup-router-observation)
|
|
38
59
|
13. [Sinks (per-client sample persistence)](#sinks-per-client-sample-persistence)
|
|
39
60
|
14. [Logging](#logging)
|
|
40
61
|
15. [Error-handling philosophy](#error-handling-philosophy)
|
|
41
|
-
16. [
|
|
42
|
-
17. [Development & extension guide](#development--extension-guide)
|
|
43
|
-
18. [Not yet implemented / roadmap](#not-yet-implemented--roadmap)
|
|
62
|
+
16. [Development & extension guide](#development--extension-guide)
|
|
44
63
|
|
|
45
64
|
---
|
|
46
65
|
|
|
@@ -72,31 +91,6 @@ produced on the client (e.g. by `@observertc/client-monitor-js`) conform to the
|
|
|
72
91
|
|
|
73
92
|
---
|
|
74
93
|
|
|
75
|
-
## Mental model
|
|
76
|
-
|
|
77
|
-
Five ideas are enough to use the whole library:
|
|
78
|
-
|
|
79
|
-
1. **One ingestion method.** `observer.accept(sample, context?)` is how data gets in.
|
|
80
|
-
Calls, clients, and peer connections are created automatically the first time their id
|
|
81
|
-
appears in a sample.
|
|
82
|
-
|
|
83
|
-
2. **A live entity tree.** `Observer → ObservedCall → ObservedClient → ObservedPeerConnection
|
|
84
|
-
→ {inbound/outbound RTP, tracks, data channels, ICE, codecs, …}`. Every node holds current
|
|
85
|
-
and cumulative metrics and is reachable by id through `Map`s on its parent.
|
|
86
|
-
|
|
87
|
-
3. **One event bus.** Everything worth subscribing to is emitted on the **`Observer`** itself
|
|
88
|
-
(it is an `EventEmitter`). Each event payload is an **object carrying the full ancestry**
|
|
89
|
-
of the entity it came from. You never have to walk the tree to subscribe.
|
|
90
|
-
|
|
91
|
-
4. **Pull or react.** You can read fields off the entities at any time (pull), and/or react to
|
|
92
|
-
events (push). The `*-updated` events fire on each processing tick.
|
|
93
|
-
|
|
94
|
-
5. **Warn, don't throw.** Operational problems (bad config, duplicate ids, closed entities,
|
|
95
|
-
malformed samples) never throw; they warn through the pluggable logger and degrade
|
|
96
|
-
gracefully (returning `undefined` or emitting `sample-rejected`).
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
94
|
## Quick start
|
|
101
95
|
|
|
102
96
|
```ts
|
|
@@ -364,6 +358,16 @@ additional field(s) on top of that scope.
|
|
|
364
358
|
| `observer-closed` | — | `observer.close()` |
|
|
365
359
|
| `sample-rejected` | `{ reason: 'observer-closed' \| 'missing-callId' \| 'missing-clientId', sample: ClientSample }` | a sample was dropped by `accept()` |
|
|
366
360
|
|
|
361
|
+
#### Mediasoup level — scope `{ observer, observedMediasoupRouter }`
|
|
362
|
+
|
|
363
|
+
| Event | Extra | Fires when |
|
|
364
|
+
|-------|-------|-----------|
|
|
365
|
+
| `mediasoup-router-added` | — | `observer.createObservedMediasoupRouter(...)` registered a router |
|
|
366
|
+
| `mediasoup-router-matched-with-call` | `{ observedCall }` | the router was matched to a call — explicitly via `callId`, or by WebRTC-transport ↔ peer-connection correlation. Emitted **once per distinct call**; the observer stores **nothing** — your handler decides what to do |
|
|
367
|
+
| `mediasoup-router-removed` | — | the underlying mediasoup router closed (its `router.observer` `close` fired) |
|
|
368
|
+
|
|
369
|
+
See [Mediasoup router observation](#mediasoup-router-observation) for the full design and examples.
|
|
370
|
+
|
|
367
371
|
#### Call level — scope `{ observer, observedCall }`
|
|
368
372
|
|
|
369
373
|
| Event | Extra | Fires when |
|
|
@@ -620,6 +624,74 @@ mediasoup set `PRODUCER_*` / `CONSUMER_*` / `DATA_PRODUCER_*` / `DATA_CONSUMER_*
|
|
|
620
624
|
`MEDIA_DEVICES_SUPPORTED_CONSTRAINTS`, `USER_MEDIA_ERROR`, `LOCAL_SDP`, `OPERATION_SYSTEM`,
|
|
621
625
|
`ENGINE`, `PLATFORM`, `BROWSER`.
|
|
622
626
|
|
|
627
|
+
### Worked example: a real `ClientSample`
|
|
628
|
+
|
|
629
|
+
Two consecutive samples from one participant ("Guest" in room `qq0iwfnd`) of an
|
|
630
|
+
edumeet/mediasoup call show what actually flows through `accept()`: a rich **join snapshot**,
|
|
631
|
+
then lean **steady-state ticks**.
|
|
632
|
+
|
|
633
|
+
**Sample 1 — the join snapshot.** Carries the one-off lifecycle `clientEvents` and device
|
|
634
|
+
`clientMetaItems` alongside the first stats. (Abbreviated; ids and times are from the real log.)
|
|
635
|
+
|
|
636
|
+
```jsonc
|
|
637
|
+
{
|
|
638
|
+
"timestamp": 1780572332518,
|
|
639
|
+
"callId": "d3dbf2f5-79be-4cb8-9d43-fb404f07ef27",
|
|
640
|
+
"clientId": "c926983c-4468-4046-ae8c-a9cabe1a1868",
|
|
641
|
+
"score": 0, // no quality measured yet on the join tick
|
|
642
|
+
"attachments": { "displayName": "Guest", "roomId": "qq0iwfnd", "actualSessionId": "d3dbf2f5-…" },
|
|
643
|
+
|
|
644
|
+
"clientEvents": [ // chronological lifecycle (12 in the real sample)
|
|
645
|
+
{ "type": "CLIENT_JOINED", "timestamp": 1780572324515 },
|
|
646
|
+
{ "type": "PEER_CONNECTION_OPENED", "timestamp": 1780572326790 }, // pc=b81c8d9d (media)
|
|
647
|
+
{ "type": "ICE_GATHERING_STATE_CHANGED", "timestamp": 1780572326811 }, // → gathering
|
|
648
|
+
{ "type": "PEER_CONNECTION_STATE_CHANGED", "timestamp": 1780572326812 }, // → connecting
|
|
649
|
+
{ "type": "PRODUCER_ADDED", "timestamp": 1780572326821 }, // producer=1abdaf82 (audio)
|
|
650
|
+
{ "type": "MEDIA_TRACK_ADDED", "timestamp": 1780572326821 }, // track=36ae42df (audio)
|
|
651
|
+
{ "type": "PEER_CONNECTION_STATE_CHANGED", "timestamp": 1780572326827 }, // → connected
|
|
652
|
+
{ "type": "PRODUCER_ADDED", "timestamp": 1780572326837 }, // producer=ba06a35b (video)
|
|
653
|
+
{ "type": "DATA_PRODUCER_CREATED", "timestamp": 1780572326853 }
|
|
654
|
+
],
|
|
655
|
+
|
|
656
|
+
"clientMetaItems": [ // environment & devices, one-off (10 in the real sample)
|
|
657
|
+
{ "type": "USER_AGENT_DATA", "payload": "{…Chrome 148 / macOS…}" },
|
|
658
|
+
{ "type": "MEDIA_DEVICE", "payload": "{…\"BRIO 4K Stream Edition\"…}" }
|
|
659
|
+
// …mic / camera / speaker devices…
|
|
660
|
+
],
|
|
661
|
+
|
|
662
|
+
"peerConnections": [
|
|
663
|
+
{
|
|
664
|
+
"peerConnectionId": "b81c8d9d-…", // the media PC — Guest publishes to the SFU
|
|
665
|
+
"outboundRtps": [ /* audio + video */ ],
|
|
666
|
+
"outboundTracks": [ /* mic + camera: label, settings, capabilities */ ],
|
|
667
|
+
"remoteInboundRtps": [ /* RTCP feedback from the SFU */ ],
|
|
668
|
+
"codecs": [ /* … */ ], "iceTransports": [ /* … */ ],
|
|
669
|
+
"iceCandidatePairs": [ /* … */ ], "dataChannels": [ /* … */ ]
|
|
670
|
+
},
|
|
671
|
+
{ "peerConnectionId": "8635acb7-…", "peerConnectionTransports": [ /* … */ ] } // signaling-only PC
|
|
672
|
+
]
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
What `accept()` does with it, in order — each step emits on the bus with full ancestry:
|
|
677
|
+
|
|
678
|
+
1. lazily creates the `ObservedCall` → **`call-added`**;
|
|
679
|
+
2. creates the `ObservedClient` → **`client-added`**, then **`client-joined`** (from `CLIENT_JOINED`);
|
|
680
|
+
3. creates an `ObservedPeerConnection` per entry → **`peer-connection-added`** (×2 here);
|
|
681
|
+
4. creates an `ObservedOutboundTrack` per track → **`outbound-track-added`**, plus the matching
|
|
682
|
+
**`outbound-rtp-added`**;
|
|
683
|
+
5. replays the device list as **`client-metadata`** events and the lifecycle items as
|
|
684
|
+
**`client-event`**; and finally **`client-updated`** for the whole tick.
|
|
685
|
+
|
|
686
|
+
`attachments.roomId` lands on `observedClient.attachments` (read it on `client-updated`, **not** at
|
|
687
|
+
creation — see [Ingestion](#ingestion-accept-context--lifecycle)).
|
|
688
|
+
|
|
689
|
+
**Sample 2 — a steady-state tick** (~8 s later): same `callId` / `clientId`, **no** new
|
|
690
|
+
`clientEvents` or `clientMetaItems`, just refreshed `peerConnections` stats. Each PC now scores `5`
|
|
691
|
+
and the aggregate client `score` is `4.74` — a healthy call. This is the shape of nearly every
|
|
692
|
+
sample: each tick refreshes metrics and fires the `*-updated` events, while the heavy join
|
|
693
|
+
snapshot happens only once.
|
|
694
|
+
|
|
623
695
|
---
|
|
624
696
|
|
|
625
697
|
## Detectors (server-side extension point)
|
|
@@ -716,6 +788,135 @@ For the mediasoup factory, the application puts `producerId` / `consumerId` (and
|
|
|
716
788
|
|
|
717
789
|
---
|
|
718
790
|
|
|
791
|
+
## Mediasoup router observation
|
|
792
|
+
|
|
793
|
+
Everything above is built from the **client-reported** `ClientSample`. When you run a
|
|
794
|
+
[mediasoup](https://mediasoup.org) SFU you also have the **server's own** ground truth — its
|
|
795
|
+
routers, transports, producers, consumers and data channels, with exact lifetimes and state
|
|
796
|
+
transitions. `ObservedMediasoupRouter` captures that server-side view into a
|
|
797
|
+
**`MediasoupRouterSample`**, completely independent of the client sample pipeline.
|
|
798
|
+
|
|
799
|
+
### The concept
|
|
800
|
+
|
|
801
|
+
You hand the observer a live mediasoup `Router`; it attaches to mediasoup's own `observer` API and,
|
|
802
|
+
from then on, **passively records** the router's topology and lifecycle — with no polling and no
|
|
803
|
+
changes to your media code:
|
|
804
|
+
|
|
805
|
+
- new transports (`webrtc` / `plain` / `pipe` / `direct`), their selected `tuple`, and ICE state
|
|
806
|
+
transitions;
|
|
807
|
+
- producers (codec, SSRCs/RIDs, `pause`/`resume`) and consumers (`pause`/`resume`,
|
|
808
|
+
`producerPaused`/`producerResumed`);
|
|
809
|
+
- data producers and data consumers;
|
|
810
|
+
- `createdAt` / `closedAt` for every entity above.
|
|
811
|
+
|
|
812
|
+
All of it accumulates on `observedMediasoupRouter.sample` (a `MediasoupRouterSample` — see
|
|
813
|
+
[`src/schema/MediasoupRouter.ts`](./src/schema/MediasoupRouter.ts)). This is a *Sample*, not a
|
|
814
|
+
*Report*: it mirrors the naming of `ClientSample` and is yours to snapshot, persist, or correlate.
|
|
815
|
+
|
|
816
|
+
### Matching a router to a call — by **event**, not by storage
|
|
817
|
+
|
|
818
|
+
A router belongs to one or more calls, but **the observer does not store the router (or its sample)
|
|
819
|
+
on the `ObservedCall`.** Instead, when a match is found it emits
|
|
820
|
+
**`mediasoup-router-matched-with-call`** and steps back — *your application* decides what the
|
|
821
|
+
pairing means. Stamp the `callId` into the router's `appData`, build your own index, attach the
|
|
822
|
+
sample to the call in your database — whatever fits your system. The library stays unopinionated and
|
|
823
|
+
loosely coupled.
|
|
824
|
+
|
|
825
|
+
There are two ways a match is discovered, controlled by the settings you pass to
|
|
826
|
+
`createObservedMediasoupRouter`:
|
|
827
|
+
|
|
828
|
+
- **Explicit (`callId`)** — you already know the call. If that call currently exists,
|
|
829
|
+
`mediasoup-router-matched-with-call` fires immediately.
|
|
830
|
+
- **Implicit (`bindCallByWebRtcTransportId: true`)** — let the observer discover it. As peer
|
|
831
|
+
connections are observed (`peer-connection-added`), the observer checks whether the peer
|
|
832
|
+
connection's id is one of the router's WebRTC transport ids. On a hit, it's a match. It emits
|
|
833
|
+
**once per distinct call**, and keeps watching so additional calls sharing the router can still
|
|
834
|
+
match later. The internal listener is removed automatically when the router closes or the observer
|
|
835
|
+
closes.
|
|
836
|
+
|
|
837
|
+
When the underlying mediasoup router closes, its `close` propagates to `ObservedMediasoupRouter`,
|
|
838
|
+
which emits **`mediasoup-router-removed`**. That is your cue to do whatever cleanup or persistence
|
|
839
|
+
you want with the now-final `sample` — again, the observer itself keeps nothing.
|
|
840
|
+
|
|
841
|
+
### Options — `observer.createObservedMediasoupRouter(settings)`
|
|
842
|
+
|
|
843
|
+
| Field | Type | Required | Meaning |
|
|
844
|
+
|-------|------|----------|---------|
|
|
845
|
+
| `router` | `mediasoup.types.Router` | yes | the live router to observe; the observer attaches to `router.observer` |
|
|
846
|
+
| `routerId` | `string` | yes | your id for the router (the sample also carries `router.id`) |
|
|
847
|
+
| `appData` | `Record<string, unknown>` | no | application-owned bag on the `ObservedMediasoupRouter` (e.g. where you record the matched `callId`) |
|
|
848
|
+
| `attachments` | `Record<string, unknown>` | no | free-form data copied onto `sample.attachments` |
|
|
849
|
+
| `callId` | `string` | no | **explicit match**: emit `mediasoup-router-matched-with-call` now if this call exists |
|
|
850
|
+
| `bindCallByWebRtcTransportId` | `boolean` | no | **implicit match**: discover the call(s) by correlating WebRTC transport ids with peer-connection ids |
|
|
851
|
+
|
|
852
|
+
Returns the `ObservedMediasoupRouter`, or `undefined` if the observer is closed (a router with the
|
|
853
|
+
same id returns the existing instance — both warn).
|
|
854
|
+
|
|
855
|
+
Useful members on the returned object: `.sample` (the `MediasoupRouterSample`), `.appData`,
|
|
856
|
+
`.attachments` (getter over `sample.attachments`), `.webrtcTransportIds: Set<string>`, `.id`,
|
|
857
|
+
`.close()`.
|
|
858
|
+
|
|
859
|
+
### Example
|
|
860
|
+
|
|
861
|
+
```ts
|
|
862
|
+
import { Observer } from '@observertc/observer-js';
|
|
863
|
+
import type { ObservedMediasoupRouterScope, ObservedCallScope } from '@observertc/observer-js';
|
|
864
|
+
|
|
865
|
+
const observer = new Observer();
|
|
866
|
+
|
|
867
|
+
// 1) Feed client samples as usual so the observer knows about calls & peer connections.
|
|
868
|
+
// (e.g. transport-layer: observer.accept(clientSample, context))
|
|
869
|
+
|
|
870
|
+
// 2) Observe the SFU side. Let the observer discover which call this router serves.
|
|
871
|
+
const router = /* your mediasoup router */ undefined as any;
|
|
872
|
+
const observedRouter = observer.createObservedMediasoupRouter({
|
|
873
|
+
router,
|
|
874
|
+
routerId: router.id,
|
|
875
|
+
bindCallByWebRtcTransportId: true, // discover the call by peer-connection correlation
|
|
876
|
+
appData: {}, // we'll record the matched callId here
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// 3) The observer found a call for the router — WE decide what to do with the pairing.
|
|
880
|
+
observer.on('mediasoup-router-matched-with-call',
|
|
881
|
+
({ observedMediasoupRouter, observedCall }: ObservedMediasoupRouterScope & ObservedCallScope) => {
|
|
882
|
+
// e.g. remember the association on the router's appData…
|
|
883
|
+
observedMediasoupRouter.appData.callId = observedCall.callId;
|
|
884
|
+
// …or attach the live server sample to the call in your own store:
|
|
885
|
+
myStore.linkRouterToCall(observedCall.callId, observedMediasoupRouter.sample);
|
|
886
|
+
},
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
// 4) The router closed — WE decide what to persist/forward with the final sample.
|
|
890
|
+
observer.on('mediasoup-router-removed', ({ observedMediasoupRouter }: ObservedMediasoupRouterScope) => {
|
|
891
|
+
const callId = observedMediasoupRouter.appData.callId as string | undefined;
|
|
892
|
+
myStore.saveRouterSample(callId, observedMediasoupRouter.sample);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
// (optional) react to the router being registered at all:
|
|
896
|
+
observer.on('mediasoup-router-added', ({ observedMediasoupRouter }) => {
|
|
897
|
+
console.log('observing router', observedMediasoupRouter.id);
|
|
898
|
+
});
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
If you already know the call, skip discovery and match explicitly:
|
|
902
|
+
|
|
903
|
+
```ts
|
|
904
|
+
observer.createObservedMediasoupRouter({ router, routerId: router.id, callId });
|
|
905
|
+
// → `mediasoup-router-matched-with-call` fires immediately if `callId` is a known call
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
### Why event-driven instead of storing on the call
|
|
909
|
+
|
|
910
|
+
- **Loose coupling.** The call model stays about client telemetry; the SFU view lives on its own
|
|
911
|
+
object and is associated only if and how *you* choose.
|
|
912
|
+
- **You own the association.** One router may serve multiple calls, a call may be served by multiple
|
|
913
|
+
routers, and the right place to keep that mapping is application-specific — so the observer hands
|
|
914
|
+
you the match and the final sample and gets out of the way.
|
|
915
|
+
- **No silent accumulation.** Nothing is appended to `ObservedCall`, so there is no hidden growth or
|
|
916
|
+
lifetime you have to reason about; the router sample lives exactly as long as you keep a reference.
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
719
920
|
## Sinks (per-client sample persistence)
|
|
720
921
|
|
|
721
922
|
A **sink** receives the samples a client accepts — for archival, streaming, or later offline
|
|
@@ -878,48 +1079,6 @@ are caught by `accept()` and surfaced as a warning.
|
|
|
878
1079
|
|
|
879
1080
|
---
|
|
880
1081
|
|
|
881
|
-
## Public exports
|
|
882
|
-
|
|
883
|
-
```ts
|
|
884
|
-
// Entry: src/index.ts
|
|
885
|
-
export { Observer } from './Observer';
|
|
886
|
-
export type { ObserverEvents, SampleRejectedReason, AcceptContext, CallAppDataFactory, ClientAppDataFactory } from './Observer';
|
|
887
|
-
export type { ObserverEventBase, ObservedCallScope, ObservedClientScope, ObservedPeerConnectionScope } from './ObserverEvents';
|
|
888
|
-
|
|
889
|
-
export { ObservedCall, ObservedClient, ObservedPeerConnection } from './…';
|
|
890
|
-
export { ObservedInboundTrack, ObservedOutboundTrack } from './…';
|
|
891
|
-
export { ObservedInboundRtp, ObservedOutboundRtp, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp } from './…';
|
|
892
|
-
export { ObservedMediaSource, ObservedMediaPlayout, ObservedCodec, ObservedCertificate, ObservedDataChannel } from './…';
|
|
893
|
-
export { ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedPeerConnectionTransport } from './…';
|
|
894
|
-
|
|
895
|
-
export { ClientSample, ClientIssue, ClientEvent, ClientMetaData } from './schema/ClientSample';
|
|
896
|
-
export { ClientEventTypes } from './schema/ClientEventTypes';
|
|
897
|
-
export { ClientMetaTypes } from './schema/ClientMetaTypes';
|
|
898
|
-
|
|
899
|
-
export { ScoreCalculator } from './scores/ScoreCalculator';
|
|
900
|
-
export { Detectors } from './detectors/Detectors';
|
|
901
|
-
export type { Detector } from './detectors/Detector';
|
|
902
|
-
|
|
903
|
-
export { createLogger, setObserverLogger } from './common/logger';
|
|
904
|
-
export type { Logger, ObserverLogger } from './common/logger';
|
|
905
|
-
|
|
906
|
-
// sinks: base class (subclass it for a custom destination) + built-ins
|
|
907
|
-
export { ClientSampleSink } from './sinks/ClientSampleSink';
|
|
908
|
-
export type { ClientSampleSinkEvents, ClientSampleSinkFactory } from './sinks/ClientSampleSink';
|
|
909
|
-
export { JsonlFileSink, createJsonlFileSink, createJsonlFileSinkFactory } from './sinks/JsonlFileSink';
|
|
910
|
-
export type { JsonlFileSinkOptions, JsonlFileSinkFactoryOptions } from './sinks/JsonlFileSink';
|
|
911
|
-
export { InMemorySink, createInMemorySink } from './sinks/InMemorySink';
|
|
912
|
-
|
|
913
|
-
export { Middleware } from './common/Middleware';
|
|
914
|
-
|
|
915
|
-
// remote track correlation
|
|
916
|
-
export { RemoteTrackResolver } from './utils/RemoteTrackResolver';
|
|
917
|
-
export type { RemoteTrackResolvers, RemoteTrackResolverFactory } from './utils/RemoteTrackResolver';
|
|
918
|
-
export { createDefaultMediasoupRemoteTrackResolverFactory, createP2pRemoteTrackResolverFactory } from './utils/RemoteTrackResolverFactories';
|
|
919
|
-
```
|
|
920
|
-
|
|
921
|
-
---
|
|
922
|
-
|
|
923
1082
|
## Development & extension guide
|
|
924
1083
|
|
|
925
1084
|
```bash
|
|
@@ -969,24 +1128,6 @@ event map + scope types), `detectors/` (`Detector`, `Detectors`), `scores/`, `up
|
|
|
969
1128
|
|
|
970
1129
|
---
|
|
971
1130
|
|
|
972
|
-
## Not yet implemented / roadmap
|
|
973
|
-
|
|
974
|
-
For an agent continuing the work, these are explicitly **not** present yet:
|
|
975
|
-
|
|
976
|
-
- **Tests.** There is currently only a placeholder spec. The two `accept()` methods
|
|
977
|
-
(`ObservedClient`, `ObservedPeerConnection`) are the priority for characterization tests. (A
|
|
978
|
-
CI gate running lint + typecheck + test is already in place — see `.github/workflows/ci.yml`.)
|
|
979
|
-
- **Built-in detectors / quality classifier.** The registry exists; concrete server-side
|
|
980
|
-
detectors (e.g. producer→consumer delivery mismatch, quality outlier, asymmetric media) are
|
|
981
|
-
to be designed.
|
|
982
|
-
- **Per-tick snapshot API.** A serializable snapshot per `*-updated` tick to replace consuming
|
|
983
|
-
the fine-grained `*-updated` events (under consideration).
|
|
984
|
-
- **Monotonic-timestamp / clock-skew handling** for client clock jumps.
|
|
985
|
-
- **Batch `processSamples()`** entry point for offline analysis of recorded sample streams.
|
|
986
|
-
- **Expanded derived metrics** (jitter-buffer delay, concealment, freeze fraction, encode/decode
|
|
987
|
-
CPU, quality-limitation breakdown, etc.) and first-class producer/consumer & PC-direction
|
|
988
|
-
fields beyond `attachments`.
|
|
989
|
-
|
|
990
1131
|
## License
|
|
991
1132
|
|
|
992
1133
|
Apache-2.0. Part of the [ObserverTC](https://github.com/observertc) ecosystem.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
+
import { types } from 'mediasoup';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* The WebRTC app provided custom stats payload
|
|
@@ -2082,6 +2083,179 @@ type ClientSampleSinkFactory = (params: {
|
|
|
2082
2083
|
observedCall: ObservedCall;
|
|
2083
2084
|
}) => ClientSampleSink | undefined;
|
|
2084
2085
|
|
|
2086
|
+
/** mediasoup `TransportTuple` (the selected local/remote address pair of a transport). */
|
|
2087
|
+
type TransportTuple = {
|
|
2088
|
+
localAddress: string;
|
|
2089
|
+
localPort: number;
|
|
2090
|
+
remoteIp?: string;
|
|
2091
|
+
remotePort?: number;
|
|
2092
|
+
protocol: 'udp' | 'tcp';
|
|
2093
|
+
};
|
|
2094
|
+
/** mediasoup `RtpCodecParameters` (the negotiated codec of a producer/consumer). */
|
|
2095
|
+
type RtpCodecParameters = {
|
|
2096
|
+
mimeType: string;
|
|
2097
|
+
payloadType: number;
|
|
2098
|
+
clockRate: number;
|
|
2099
|
+
channels?: number;
|
|
2100
|
+
parameters?: Record<string, unknown>;
|
|
2101
|
+
rtcpFeedback?: {
|
|
2102
|
+
type: string;
|
|
2103
|
+
parameter?: string;
|
|
2104
|
+
}[];
|
|
2105
|
+
};
|
|
2106
|
+
type SampleHistoryItem<T extends string> = Record<string, unknown> & {
|
|
2107
|
+
type: T;
|
|
2108
|
+
timestamp: number;
|
|
2109
|
+
};
|
|
2110
|
+
type MediasoupRouterSample = Record<string, unknown> & {
|
|
2111
|
+
routerId: string;
|
|
2112
|
+
attachments: Record<string, unknown>;
|
|
2113
|
+
createdAt: number;
|
|
2114
|
+
closedAt?: number;
|
|
2115
|
+
producers: MediasoupProducerSample[];
|
|
2116
|
+
consumers: MediasoupConsumerSample[];
|
|
2117
|
+
dataProducers: MediasoupDataProducerSample[];
|
|
2118
|
+
dataConsumers: MediasoupDataConsumerSample[];
|
|
2119
|
+
transports: MediasoupTransportSample[];
|
|
2120
|
+
};
|
|
2121
|
+
type MediasoupWebRtcTransportSampleEventTypes = 'icestate-changed-to-new' | 'icestate-changed-to-connected' | 'icestate-changed-to-completed' | 'icestate-changed-to-disconnected' | 'icestate-changed-to-closed' | 'dtlsstate-changed-to-new' | 'dtlsstate-changed-to-connecting' | 'dtlsstate-changed-to-connected' | 'dtlsstate-changed-to-failed' | 'dtlsstate-changed-to-closed' | 'sctpstate-changed-to-new' | 'sctpstate-changed-to-connecting' | 'sctpstate-changed-to-connected' | 'sctpstate-changed-to-failed' | 'sctpstate-changed-to-closed' | 'iceselectedtuple-changed';
|
|
2122
|
+
type MediasoupWebRtcTransportSampleEventMap = {
|
|
2123
|
+
[K in MediasoupWebRtcTransportSampleEventTypes]: SampleHistoryItem<K>;
|
|
2124
|
+
}[MediasoupWebRtcTransportSampleEventTypes];
|
|
2125
|
+
type MediasoupWebRtcTransportSample = {
|
|
2126
|
+
type: 'webrtc';
|
|
2127
|
+
history: MediasoupWebRtcTransportSampleEventMap[];
|
|
2128
|
+
};
|
|
2129
|
+
type MediasoupPlainTransportSampleEventTypes = 'sctpstate-changed-to-new' | 'sctpstate-changed-to-connecting' | 'sctpstate-changed-to-connected' | 'sctpstate-changed-to-failed' | 'sctpstate-changed-to-closed' | 'tuple-changed' | 'rtcptuple-changed';
|
|
2130
|
+
type MediasoupPlainTransportSampleEventMap = {
|
|
2131
|
+
[K in MediasoupPlainTransportSampleEventTypes]: SampleHistoryItem<K>;
|
|
2132
|
+
}[MediasoupPlainTransportSampleEventTypes];
|
|
2133
|
+
type MediasoupPlainTransportSample = {
|
|
2134
|
+
type: 'plain';
|
|
2135
|
+
rtcpTuple?: TransportTuple;
|
|
2136
|
+
history: MediasoupPlainTransportSampleEventMap[];
|
|
2137
|
+
};
|
|
2138
|
+
type MediasoupPipeTransportSampleEventTypes = 'sctpstate-changed-to-new' | 'sctpstate-changed-to-connecting' | 'sctpstate-changed-to-connected' | 'sctpstate-changed-to-failed' | 'sctpstate-changed-to-closed';
|
|
2139
|
+
type MediasoupPipeTransportSampleEventMap = {
|
|
2140
|
+
[K in MediasoupPipeTransportSampleEventTypes]: SampleHistoryItem<K>;
|
|
2141
|
+
}[MediasoupPipeTransportSampleEventTypes];
|
|
2142
|
+
type MediasoupPipeTransportSample = {
|
|
2143
|
+
type: 'pipe';
|
|
2144
|
+
history: MediasoupPipeTransportSampleEventMap[];
|
|
2145
|
+
};
|
|
2146
|
+
type MediasoupDirectTransportSampleEventTypes = never;
|
|
2147
|
+
type MediasoupDirectTransportSampleEventMap = {
|
|
2148
|
+
[K in MediasoupDirectTransportSampleEventTypes]: SampleHistoryItem<K>;
|
|
2149
|
+
}[MediasoupDirectTransportSampleEventTypes];
|
|
2150
|
+
type MediasoupDirectTransportSample = {
|
|
2151
|
+
type: 'direct';
|
|
2152
|
+
history: MediasoupDirectTransportSampleEventMap[];
|
|
2153
|
+
};
|
|
2154
|
+
type MediasoupTransportSample = Record<string, unknown> & {
|
|
2155
|
+
id: string;
|
|
2156
|
+
createdAt: number;
|
|
2157
|
+
connectedAt?: number;
|
|
2158
|
+
closedAt?: number;
|
|
2159
|
+
tuple?: TransportTuple;
|
|
2160
|
+
} & (MediasoupWebRtcTransportSample | MediasoupPlainTransportSample | MediasoupPipeTransportSample | MediasoupDirectTransportSample);
|
|
2161
|
+
type MediasoupProducerSampleEventMap = {
|
|
2162
|
+
'pause': undefined;
|
|
2163
|
+
'resume': undefined;
|
|
2164
|
+
'degraded': undefined;
|
|
2165
|
+
'restored': undefined;
|
|
2166
|
+
};
|
|
2167
|
+
type MediasoupProducerSampleEvent = {
|
|
2168
|
+
[K in keyof MediasoupProducerSampleEventMap]: SampleHistoryItem<K>;
|
|
2169
|
+
}[keyof MediasoupProducerSampleEventMap];
|
|
2170
|
+
type MediasoupProducerSample = Record<string, unknown> & {
|
|
2171
|
+
id: string;
|
|
2172
|
+
transportId: string;
|
|
2173
|
+
createdAt: number;
|
|
2174
|
+
closedAt?: number;
|
|
2175
|
+
codecInfo: RtpCodecParameters;
|
|
2176
|
+
kind: 'audio' | 'video';
|
|
2177
|
+
ssrcs?: number[];
|
|
2178
|
+
rids?: string[];
|
|
2179
|
+
history: MediasoupProducerSampleEvent[];
|
|
2180
|
+
};
|
|
2181
|
+
type MediasoupConsumerSampleEventMap = {
|
|
2182
|
+
'pause': undefined;
|
|
2183
|
+
'resume': undefined;
|
|
2184
|
+
'producerPaused': undefined;
|
|
2185
|
+
'producerResumed': undefined;
|
|
2186
|
+
'stopped': undefined;
|
|
2187
|
+
'started': undefined;
|
|
2188
|
+
'degraded': undefined;
|
|
2189
|
+
'restored': undefined;
|
|
2190
|
+
};
|
|
2191
|
+
type MediasoupConsumerSampleEvent = {
|
|
2192
|
+
[K in keyof MediasoupConsumerSampleEventMap]: SampleHistoryItem<K>;
|
|
2193
|
+
}[keyof MediasoupConsumerSampleEventMap];
|
|
2194
|
+
type MediasoupConsumerSample = Record<string, unknown> & {
|
|
2195
|
+
id: string;
|
|
2196
|
+
producerId: string;
|
|
2197
|
+
transportId: string;
|
|
2198
|
+
createdAt: number;
|
|
2199
|
+
closedAt?: number;
|
|
2200
|
+
kind: 'audio' | 'video';
|
|
2201
|
+
history: MediasoupConsumerSampleEvent[];
|
|
2202
|
+
};
|
|
2203
|
+
type MediasoupDataProducerSample = Record<string, unknown> & {
|
|
2204
|
+
id: string;
|
|
2205
|
+
transportId: string;
|
|
2206
|
+
createdAt: number;
|
|
2207
|
+
closedAt?: number;
|
|
2208
|
+
label: string;
|
|
2209
|
+
protocol: string;
|
|
2210
|
+
};
|
|
2211
|
+
type MediasoupDataConsumerSample = Record<string, unknown> & {
|
|
2212
|
+
id: string;
|
|
2213
|
+
dataProducerId: string;
|
|
2214
|
+
transportId: string;
|
|
2215
|
+
createdAt: number;
|
|
2216
|
+
closedAt?: number;
|
|
2217
|
+
label: string;
|
|
2218
|
+
protocol: string;
|
|
2219
|
+
};
|
|
2220
|
+
|
|
2221
|
+
type ObservedMediasoupRouterSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = {
|
|
2222
|
+
routerId: string;
|
|
2223
|
+
router: types.Router;
|
|
2224
|
+
appData?: AppData;
|
|
2225
|
+
attachments?: Record<string, unknown>;
|
|
2226
|
+
};
|
|
2227
|
+
type ObservedMediasoupRouterEvents = {
|
|
2228
|
+
close: [];
|
|
2229
|
+
};
|
|
2230
|
+
declare interface ObservedMediasoupRouter {
|
|
2231
|
+
on<U extends keyof ObservedMediasoupRouterEvents>(event: U, listener: (...args: ObservedMediasoupRouterEvents[U]) => void): this;
|
|
2232
|
+
off<U extends keyof ObservedMediasoupRouterEvents>(event: U, listener: (...args: ObservedMediasoupRouterEvents[U]) => void): this;
|
|
2233
|
+
once<U extends keyof ObservedMediasoupRouterEvents>(event: U, listener: (...args: ObservedMediasoupRouterEvents[U]) => void): this;
|
|
2234
|
+
emit<U extends keyof ObservedMediasoupRouterEvents>(event: U, ...args: ObservedMediasoupRouterEvents[U]): boolean;
|
|
2235
|
+
}
|
|
2236
|
+
declare class ObservedMediasoupRouter<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
|
|
2237
|
+
readonly router: types.Router;
|
|
2238
|
+
readonly sample: MediasoupRouterSample;
|
|
2239
|
+
appData: AppData;
|
|
2240
|
+
readonly webrtcTransportIds: Set<string>;
|
|
2241
|
+
get attachments(): Record<string, unknown>;
|
|
2242
|
+
closed: boolean;
|
|
2243
|
+
constructor(settings: ObservedMediasoupRouterSettings<AppData>);
|
|
2244
|
+
get id(): string;
|
|
2245
|
+
addTransport: (transport: types.Transport) => void;
|
|
2246
|
+
addWebRtcTransport(transport: types.WebRtcTransport): void;
|
|
2247
|
+
addPlainTransport(transport: types.PlainTransport): void;
|
|
2248
|
+
addPipeTransport(transport: types.PipeTransport): void;
|
|
2249
|
+
addDirectTransport(transport: types.DirectTransport): void;
|
|
2250
|
+
addProducer(transport: types.Transport, producer: types.Producer): void;
|
|
2251
|
+
addConsumer(transport: types.Transport, consumer: types.Consumer): void;
|
|
2252
|
+
addDataProducer(transport: types.Transport, dataProducer: types.DataProducer): void;
|
|
2253
|
+
addDataConsumer(transport: types.Transport, dataConsumer: types.DataConsumer): void;
|
|
2254
|
+
close(): void;
|
|
2255
|
+
private attachRouterListeners;
|
|
2256
|
+
private _attachTransportObserverListeners;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2085
2259
|
/**
|
|
2086
2260
|
* The Observer is the single event bus for the whole hierarchy. Every event
|
|
2087
2261
|
* worth subscribing to is emitted on the Observer with a payload object that
|
|
@@ -2109,6 +2283,9 @@ type ObservedClientScope = ObservedCallScope & {
|
|
|
2109
2283
|
type ObservedPeerConnectionScope = ObservedClientScope & {
|
|
2110
2284
|
observedPeerConnection: ObservedPeerConnection;
|
|
2111
2285
|
};
|
|
2286
|
+
type ObservedMediasoupRouterScope = Omit<ObserverEventBase, 'context'> & {
|
|
2287
|
+
observedMediasoupRouter: ObservedMediasoupRouter;
|
|
2288
|
+
};
|
|
2112
2289
|
type ObserverEvents = {
|
|
2113
2290
|
'observer-updated': [ObserverEventBase];
|
|
2114
2291
|
'observer-closed': [ObserverEventBase];
|
|
@@ -2116,6 +2293,9 @@ type ObserverEvents = {
|
|
|
2116
2293
|
reason: SampleRejectedReason;
|
|
2117
2294
|
sample: ClientSample;
|
|
2118
2295
|
}];
|
|
2296
|
+
'mediasoup-router-added': [ObservedMediasoupRouterScope];
|
|
2297
|
+
'mediasoup-router-removed': [ObservedMediasoupRouterScope];
|
|
2298
|
+
'mediasoup-router-matched-with-call': [ObservedMediasoupRouterScope & ObservedCallScope];
|
|
2119
2299
|
'call-added': [ObservedCallScope];
|
|
2120
2300
|
'call-updated': [ObservedCallScope];
|
|
2121
2301
|
'call-closed': [ObservedCallScope];
|
|
@@ -2636,6 +2816,7 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
2636
2816
|
readonly config: ObserverConfig<AppData>;
|
|
2637
2817
|
readonly observedTURN: ObservedTURN;
|
|
2638
2818
|
readonly observedCalls: Map<string, ObservedCall<Record<string, unknown>>>;
|
|
2819
|
+
readonly observedMediasoupRouters: Map<string, ObservedMediasoupRouter<Record<string, unknown>>>;
|
|
2639
2820
|
updater?: Updater;
|
|
2640
2821
|
/** Ancestry base shared by all Observer-bus events originating at the observer. */
|
|
2641
2822
|
readonly eventScope: ObserverEventBase;
|
|
@@ -2656,6 +2837,10 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
2656
2837
|
getObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(callId: string): ObservedCall<T> | undefined;
|
|
2657
2838
|
createObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
2658
2839
|
getOrCreateObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
2840
|
+
createObservedMediasoupRouter<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedMediasoupRouterSettings<T> & {
|
|
2841
|
+
callId?: string;
|
|
2842
|
+
bindCallByWebRtcTransportId?: boolean;
|
|
2843
|
+
}): ObservedMediasoupRouter<Record<string, unknown>> | undefined;
|
|
2659
2844
|
close(): void;
|
|
2660
2845
|
accept(sample: ClientSample, context?: AcceptContext): void;
|
|
2661
2846
|
update(): void;
|
|
@@ -2768,4 +2953,4 @@ declare function createInMemorySink(samples?: ClientSample[]): InMemorySink;
|
|
|
2768
2953
|
declare function createDefaultMediasoupRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
2769
2954
|
declare function createP2pRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
2770
2955
|
|
|
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 };
|
|
2956
|
+
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 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 MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, ObservedCall, type ObservedCallScope, ObservedCertificate, ObservedClient, type ObservedClientScope, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, 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 };
|