@observertc/observer-js 1.0.0-beta.11 → 1.0.0-beta.12
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 +76 -42
- package/dist/index.d.mts +16 -4
- package/dist/index.d.ts +16 -4
- package/dist/index.js +45 -117
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -117
- package/dist/index.mjs.map +1 -1
- package/llms-full.txt +77 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -245,8 +245,7 @@ observer.addAcceptMiddleware(route, filter);
|
|
|
245
245
|
// observer.removeAcceptMiddleware(route);
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
This is a lightweight global injection point
|
|
249
|
-
`ClientSampleProcessor` pipeline in the roadmap. When no middleware is registered, `accept()`
|
|
248
|
+
This is a lightweight global injection point. When no middleware is registered, `accept()`
|
|
250
249
|
dispatches directly with no overhead.
|
|
251
250
|
|
|
252
251
|
### `context` (the `AcceptContext`)
|
|
@@ -409,7 +408,6 @@ See [Mediasoup router observation](#mediasoup-router-observation) for the full d
|
|
|
409
408
|
| `peer-connection-added` / `peer-connection-closed` | — | lifecycle of the PC |
|
|
410
409
|
| `peer-connection-updated` | `{ context?: AcceptContext }` | the PC processed a sample |
|
|
411
410
|
| `ice-connection-state-changed` / `ice-gathering-state-changed` / `connection-state-changed` | `{ state: string }` | driven by client events |
|
|
412
|
-
| `selected-candidate-pair-changed` | — | *declared; not currently emitted* |
|
|
413
411
|
| `inbound-track-added` / `-updated` / `-removed` / `-muted` / `-unmuted` | `{ observedInboundTrack }` | |
|
|
414
412
|
| `outbound-track-added` / `-updated` / `-removed` / `-muted` / `-unmuted` | `{ observedOutboundTrack }` | |
|
|
415
413
|
| `inbound-rtp-added` / `-updated` / `-removed` | `{ observedInboundRtp }` | `-updated` fires every tick |
|
|
@@ -462,8 +460,8 @@ type ObserverConfig<AppData = Record<string, unknown>> = {
|
|
|
462
460
|
createClientAppData?: (p: { clientId: string; observedCall: ObservedCall }) => Record<string, unknown>;
|
|
463
461
|
// sink factory — produces a per-client sink that receives every accepted sample (see Sinks).
|
|
464
462
|
createClientSink?: (p: { clientId: string; observedCall: ObservedCall }) => ClientSampleSink | undefined;
|
|
465
|
-
// track-resolver factory — produces a call's RemoteTrackResolver (see Remote track resolution).
|
|
466
|
-
|
|
463
|
+
// remote-track-resolver factory — produces a call's RemoteTrackResolver (see Remote track resolution).
|
|
464
|
+
createRemoteTrackResolver?: (observedCall: ObservedCall) => RemoteTrackResolver | undefined;
|
|
467
465
|
};
|
|
468
466
|
```
|
|
469
467
|
|
|
@@ -517,7 +515,7 @@ Key members:
|
|
|
517
515
|
- `addIssue(issue: ClientIssue): void` — raise a **call-level** issue → emits `call-issue`
|
|
518
516
|
- `readonly detectors: Detectors` — server-side detector registry (empty by default; see [Detectors](#detectors-server-side-extension-point))
|
|
519
517
|
- `scoreCalculator: ScoreCalculator`, `get score()`, `readonly calculatedScore`
|
|
520
|
-
- `remoteTrackResolver?: RemoteTrackResolver` — set from `ObserverConfig.
|
|
518
|
+
- `remoteTrackResolver?: RemoteTrackResolver` — set from `ObserverConfig.createRemoteTrackResolver` at call creation (see [Remote track resolution](#remote-track-resolution-mediasoup--sfu))
|
|
521
519
|
- aggregates: `numberOfIssues`, `numberOfPeerConnections`, `numberOfInboundRtpStreams`,
|
|
522
520
|
`numberOfOutboundRtpStreams`, `numberOfDataChannels`, `maxNumberOfClients`,
|
|
523
521
|
`clientsUsedTurn: Set<string>`, `startedAt?`, `endedAt?`, `closedAt?`, `closed`
|
|
@@ -752,7 +750,7 @@ class Detectors {
|
|
|
752
750
|
|
|
753
751
|
In an SFU, one participant's **outbound** track is delivered to other participants as **inbound**
|
|
754
752
|
tracks (one **publisher** → many **subscribers**). Correlation is **opt-in** per observer: set
|
|
755
|
-
`ObserverConfig.
|
|
753
|
+
`ObserverConfig.createRemoteTrackResolver`, a factory invoked when each call is created that returns the
|
|
756
754
|
call's `RemoteTrackResolver` (or `undefined` for none).
|
|
757
755
|
|
|
758
756
|
`RemoteTrackResolver` is a generic, strategy-driven class. It subscribes to the bus (filtered to
|
|
@@ -763,7 +761,7 @@ the tracks: `inboundTrack.remoteOutboundTrack` and `outboundTrack.remoteInboundT
|
|
|
763
761
|
import { Observer, createDefaultMediasoupRemoteTrackResolverFactory } from '@observertc/observer-js';
|
|
764
762
|
|
|
765
763
|
const observer = new Observer({
|
|
766
|
-
|
|
764
|
+
createRemoteTrackResolver: createDefaultMediasoupRemoteTrackResolverFactory(),
|
|
767
765
|
});
|
|
768
766
|
|
|
769
767
|
// later, given tracks (links are kept up to date as tracks come and go):
|
|
@@ -782,7 +780,7 @@ id is just whatever links a subscribed track to the published one:
|
|
|
782
780
|
import { Observer, RemoteTrackResolver } from '@observertc/observer-js';
|
|
783
781
|
|
|
784
782
|
const observer = new Observer({
|
|
785
|
-
|
|
783
|
+
createRemoteTrackResolver: (observedCall) => new RemoteTrackResolver(observedCall, {
|
|
786
784
|
resolveOutboundTrackPublisherId: (out) => out.attachments?.mediaId as string | undefined,
|
|
787
785
|
resolveInboundTrackPublisherId: (inb) => inb.attachments?.mediaId as string | undefined,
|
|
788
786
|
resolveInboundTrackSubscriberId: (inb) => inb.attachments?.subId as string | undefined, // optional
|
|
@@ -806,19 +804,37 @@ transitions. `ObservedMediasoupRouter` captures that server-side view into a
|
|
|
806
804
|
### The concept
|
|
807
805
|
|
|
808
806
|
You hand the observer a live mediasoup `Router`; it attaches to mediasoup's own `observer` API and,
|
|
809
|
-
from then on, **passively
|
|
807
|
+
from then on, **passively tracks** the router's topology and lifecycle — with no polling and no
|
|
810
808
|
changes to your media code:
|
|
811
809
|
|
|
812
|
-
- new transports (`webrtc` / `plain` / `pipe` / `direct`), their selected `tuple`,
|
|
813
|
-
transitions
|
|
810
|
+
- new transports (`webrtc` / `plain` / `pipe` / `direct`), their selected `tuple`, ICE/DTLS/SCTP
|
|
811
|
+
state transitions and `connectedAt`;
|
|
814
812
|
- producers (codec, SSRCs/RIDs, `pause`/`resume`) and consumers (`pause`/`resume`,
|
|
815
813
|
`producerPaused`/`producerResumed`);
|
|
816
814
|
- data producers and data consumers;
|
|
817
815
|
- `createdAt` / `closedAt` for every entity above.
|
|
818
816
|
|
|
819
|
-
|
|
820
|
-
[`src/schema/MediasoupRouter.ts`](./src/schema/MediasoupRouter.ts)
|
|
821
|
-
|
|
817
|
+
It keeps all of this **in memory**, in a single `MediasoupRouterSample` exposed as
|
|
818
|
+
`observedRouter.sample` — see [`src/schema/MediasoupRouter.ts`](./src/schema/MediasoupRouter.ts). The
|
|
819
|
+
sample **accumulates for the life of the router**: closed transports/producers/consumers are kept
|
|
820
|
+
(with their `closedAt` set), not removed. Read it whenever you like — it's a plain object you own.
|
|
821
|
+
|
|
822
|
+
### Memory & large meetings
|
|
823
|
+
|
|
824
|
+
This is intentionally the **simplest** approach — everything lives in memory and nothing is sampled
|
|
825
|
+
or evicted for you. That's fine for typical rooms, but be aware of the cost at scale:
|
|
826
|
+
|
|
827
|
+
- **Consumers grow as O(N²)** on a single flat router: with `N` participants each producing audio +
|
|
828
|
+
video and consuming everyone else, the sample holds roughly `2·N·(N−1)` consumer records (≈ 19,800
|
|
829
|
+
for `N` = 100).
|
|
830
|
+
- The sample is **cumulative** — closed entities and their `history` are retained — so it also grows
|
|
831
|
+
with call duration and churn (renegotiation, simulcast layer changes, rejoins).
|
|
832
|
+
|
|
833
|
+
A 100-participant flat router can therefore reach tens of MB and keep growing. There is **no built-in
|
|
834
|
+
sink, snapshotting, or eviction** — by design. **If you run large meetings, do your own sampling:**
|
|
835
|
+
on your own cadence read `observedRouter.sample` (snapshot/serialize/persist what you need), drop what
|
|
836
|
+
you don't, and close routers you no longer track. (mediasoup also typically shards routers across
|
|
837
|
+
workers/cores, which keeps any one router small.)
|
|
822
838
|
|
|
823
839
|
### Matching peer connections — by **event**, not by storage
|
|
824
840
|
|
|
@@ -841,32 +857,54 @@ router serving many participants emits one match per participant's transport. Wh
|
|
|
841
857
|
or `false`, no matching is performed and the event never fires. The internal listener is removed
|
|
842
858
|
automatically when the router closes or the observer closes.
|
|
843
859
|
|
|
860
|
+
### Ordering contract — observe the router first
|
|
861
|
+
|
|
862
|
+
Matching is **forward-only by design**, and that is sufficient because the lifecycle ordering is
|
|
863
|
+
**guaranteed, not racy**:
|
|
864
|
+
|
|
865
|
+
- `ObservedMediasoupRouter` works purely by **subscribing to mediasoup's `observer` API**, so it can
|
|
866
|
+
only see events that happen *after* it is created. You therefore create it the moment the router
|
|
867
|
+
exists — **before** any transport is added to it — and it captures the rest going forward.
|
|
868
|
+
- A mediasoup transport is always created **on the server first**; only then can the client connect
|
|
869
|
+
to it, produce/consume, and begin shipping `ClientSample`s. So a peer connection — and the
|
|
870
|
+
`peer-connection-added` event it triggers — can never appear before its server-side WebRTC
|
|
871
|
+
transport already exists (and has been observed by the router).
|
|
872
|
+
|
|
873
|
+
Put together: by the time a `peer-connection-added` fires, the router has already recorded that
|
|
874
|
+
transport's id in `webrtcTransportIds`, so a single forward-looking listener catches every match. No
|
|
875
|
+
back-scan of existing peer connections and no re-check on transport creation are needed — the
|
|
876
|
+
observer deliberately does **not** look backwards.
|
|
877
|
+
|
|
878
|
+
**Your responsibility:** call `createObservedMediasoupRouter(...)` as early as the router exists
|
|
879
|
+
(before transports are added or samples are accepted). If you register the router *after* its
|
|
880
|
+
transports are created or after the client's first sample, those events are already in the past and
|
|
881
|
+
the corresponding matches are missed.
|
|
882
|
+
|
|
844
883
|
When the underlying mediasoup router closes, its `close` propagates to `ObservedMediasoupRouter`,
|
|
845
|
-
which emits **`mediasoup-router-removed
|
|
846
|
-
|
|
884
|
+
which sets the sample's `closedAt` and emits **`mediasoup-router-removed`** — your cue to read /
|
|
885
|
+
persist the final `observedRouter.sample` and drop your reference to it.
|
|
847
886
|
|
|
848
887
|
### Options — `observer.createObservedMediasoupRouter(settings)`
|
|
849
888
|
|
|
850
889
|
| Field | Type | Required | Meaning |
|
|
851
890
|
|-------|------|----------|---------|
|
|
852
|
-
| `router` | `mediasoup.types.Router` | yes | the live router to observe; the observer attaches to `router.observer` |
|
|
853
|
-
| `routerId` | `string` | yes | your id for the router (the sample also carries `router.id`) |
|
|
891
|
+
| `router` | `mediasoup.types.Router` | yes | the live router to observe; the observer attaches to `router.observer`. `.id` and the sample's `routerId` come from `router.id` |
|
|
854
892
|
| `appData` | `Record<string, unknown>` | no | application-owned bag on the `ObservedMediasoupRouter` |
|
|
855
|
-
| `attachments` | `Record<string, unknown>` | no | free-form data
|
|
893
|
+
| `attachments` | `Record<string, unknown>` | no | free-form data; carried on `sample.attachments` |
|
|
856
894
|
| `matchPeerConnectionByWebRtcTransportId` | `boolean` | no | opt in to peer-connection matching: emit `mediasoup-router-matched-with-peer-connection` for each peer connection whose id matches one of the router's WebRTC transport ids. Omitted / `false` → no matching, the event never fires |
|
|
857
895
|
|
|
858
896
|
Peer-connection matching is **off by default**; enable it with
|
|
859
897
|
`matchPeerConnectionByWebRtcTransportId: true`. Returns the `ObservedMediasoupRouter`, or `undefined`
|
|
860
898
|
if the observer is closed (a router with the same id returns the existing instance — both warn).
|
|
861
899
|
|
|
862
|
-
Useful members on the returned object: `.sample` (the `MediasoupRouterSample
|
|
863
|
-
|
|
864
|
-
`.close()`.
|
|
900
|
+
Useful members on the returned object: `.sample` (the in-memory `MediasoupRouterSample`, with
|
|
901
|
+
`createdAt` / `closedAt?` on it), `.appData`, `.attachments`, `.webrtcTransportIds: Set<string>`,
|
|
902
|
+
`.id`, `.close()`.
|
|
865
903
|
|
|
866
904
|
### Example
|
|
867
905
|
|
|
868
906
|
```ts
|
|
869
|
-
import { Observer } from '@observertc/observer-js';
|
|
907
|
+
import { Observer, InMemorySink } from '@observertc/observer-js';
|
|
870
908
|
import type { ObservedMediasoupRouterScope, ObservedPeerConnectionScope } from '@observertc/observer-js';
|
|
871
909
|
|
|
872
910
|
const observer = new Observer();
|
|
@@ -874,46 +912,42 @@ const observer = new Observer();
|
|
|
874
912
|
// 1) Feed client samples as usual so the observer knows about calls, clients & peer connections.
|
|
875
913
|
// (e.g. transport-layer: observer.accept(clientSample, context))
|
|
876
914
|
|
|
877
|
-
// 2) Observe the SFU side
|
|
915
|
+
// 2) Observe the SFU side; opt in to peer-connection matching. State accumulates in `.sample`.
|
|
878
916
|
const router = /* your mediasoup router */ undefined as any;
|
|
879
917
|
const observedRouter = observer.createObservedMediasoupRouter({
|
|
880
918
|
router,
|
|
881
|
-
routerId: router.id,
|
|
882
919
|
matchPeerConnectionByWebRtcTransportId: true,
|
|
883
920
|
});
|
|
884
921
|
|
|
922
|
+
// For large meetings, sample it yourself on your own cadence (see "Memory & large meetings"):
|
|
923
|
+
// setInterval(() => persist(observedRouter.sample), 10_000);
|
|
924
|
+
|
|
885
925
|
// 3) Every peer connection whose id matches one of the router's WebRTC transport ids fires this —
|
|
886
926
|
// WE decide what to do with each pairing. The payload carries the full ancestry.
|
|
887
927
|
observer.on('mediasoup-router-matched-with-peer-connection',
|
|
888
|
-
({ observedMediasoupRouter, observedCall,
|
|
928
|
+
({ observedMediasoupRouter, observedCall, observedPeerConnection }:
|
|
889
929
|
ObservedMediasoupRouterScope & ObservedPeerConnectionScope) => {
|
|
890
|
-
// e.g. remember which router serves this peer connection / client…
|
|
891
930
|
(observedPeerConnection.appData ??= {}).routerId = observedMediasoupRouter.id;
|
|
892
|
-
|
|
893
|
-
myStore.linkRouterToCall(observedCall.callId, observedMediasoupRouter.sample);
|
|
931
|
+
myStore.linkRouterToCall(observedCall.callId, observedMediasoupRouter.id);
|
|
894
932
|
},
|
|
895
933
|
);
|
|
896
934
|
|
|
897
|
-
// 4) The router closed —
|
|
935
|
+
// 4) The router closed — read/persist the final state, then drop your reference.
|
|
898
936
|
observer.on('mediasoup-router-removed', ({ observedMediasoupRouter }: ObservedMediasoupRouterScope) => {
|
|
899
|
-
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
// (optional) react to the router being registered at all:
|
|
903
|
-
observer.on('mediasoup-router-added', ({ observedMediasoupRouter }) => {
|
|
904
|
-
console.log('observing router', observedMediasoupRouter.id);
|
|
937
|
+
persist(observedMediasoupRouter.sample); // its `closedAt` is set
|
|
905
938
|
});
|
|
906
939
|
```
|
|
907
940
|
|
|
908
|
-
### Why event-driven instead of storing on the call
|
|
941
|
+
### Why event-driven matching instead of storing on the call
|
|
909
942
|
|
|
910
943
|
- **Loose coupling.** The call model stays about client telemetry; the SFU view lives on its own
|
|
911
|
-
|
|
944
|
+
`ObservedMediasoupRouter` and is associated only if and how *you* choose.
|
|
912
945
|
- **You own the association.** One router serves many peer connections (across clients and calls),
|
|
913
946
|
and the right place to keep that mapping is application-specific — so the observer hands you each
|
|
914
|
-
peer-connection match and
|
|
915
|
-
- **
|
|
916
|
-
|
|
947
|
+
peer-connection match and gets out of the way.
|
|
948
|
+
- **You own the sampling.** The router sample is plain in-memory state you read on your own terms;
|
|
949
|
+
for large meetings, sample/persist it yourself (see [Memory & large meetings](#memory--large-meetings))
|
|
950
|
+
rather than relying on the library to evict — it deliberately doesn't.
|
|
917
951
|
|
|
918
952
|
---
|
|
919
953
|
|
package/dist/index.d.mts
CHANGED
|
@@ -2232,15 +2232,28 @@ declare interface ObservedMediasoupRouter {
|
|
|
2232
2232
|
once<U extends keyof ObservedMediasoupRouterEvents>(event: U, listener: (...args: ObservedMediasoupRouterEvents[U]) => void): this;
|
|
2233
2233
|
emit<U extends keyof ObservedMediasoupRouterEvents>(event: U, ...args: ObservedMediasoupRouterEvents[U]): boolean;
|
|
2234
2234
|
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Observes a live mediasoup `Router` by subscribing to its `observer` API and **accumulates** its
|
|
2237
|
+
* topology and lifecycle into an in-memory `MediasoupRouterSample` (`observedRouter.sample`):
|
|
2238
|
+
* transports, producers, consumers, data producers/consumers, their state-change history and
|
|
2239
|
+
* `createdAt` / `closedAt`. The sample grows for the life of the router (closed entities are kept,
|
|
2240
|
+
* with their `closedAt` set) and is yours to read, snapshot, or persist.
|
|
2241
|
+
*
|
|
2242
|
+
* NOTE: this is intentionally the simplest approach — everything is held in memory. For very large
|
|
2243
|
+
* routers (e.g. ~100 participants producing and consuming on one router, where consumers grow as
|
|
2244
|
+
* O(N²)) this can become substantial; in that case do your own periodic sampling/persistence and
|
|
2245
|
+
* discard what you don't need (see the README's "Memory & large meetings" note).
|
|
2246
|
+
*/
|
|
2235
2247
|
declare class ObservedMediasoupRouter<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
|
|
2236
2248
|
readonly router: types.Router;
|
|
2237
|
-
readonly sample: MediasoupRouterSample;
|
|
2238
2249
|
appData: AppData;
|
|
2250
|
+
readonly sample: MediasoupRouterSample;
|
|
2239
2251
|
readonly webrtcTransportIds: Set<string>;
|
|
2240
|
-
get attachments(): Record<string, unknown>;
|
|
2241
2252
|
closed: boolean;
|
|
2242
2253
|
constructor(settings: ObservedMediasoupRouterSettings<AppData>);
|
|
2243
2254
|
get id(): string;
|
|
2255
|
+
get attachments(): Record<string, unknown>;
|
|
2256
|
+
close(): void;
|
|
2244
2257
|
addTransport: (transport: types.Transport) => void;
|
|
2245
2258
|
addWebRtcTransport(transport: types.WebRtcTransport): void;
|
|
2246
2259
|
addPlainTransport(transport: types.PlainTransport): void;
|
|
@@ -2250,7 +2263,6 @@ declare class ObservedMediasoupRouter<AppData extends Record<string, unknown> =
|
|
|
2250
2263
|
addConsumer(transport: types.Transport, consumer: types.Consumer): void;
|
|
2251
2264
|
addDataProducer(transport: types.Transport, dataProducer: types.DataProducer): void;
|
|
2252
2265
|
addDataConsumer(transport: types.Transport, dataConsumer: types.DataConsumer): void;
|
|
2253
|
-
close(): void;
|
|
2254
2266
|
private attachRouterListeners;
|
|
2255
2267
|
private _attachTransportObserverListeners;
|
|
2256
2268
|
}
|
|
@@ -2805,7 +2817,7 @@ type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unk
|
|
|
2805
2817
|
* (`createDefaultMediasoupRemoteTrackResolverFactory()` / `createP2pRemoteTrackResolverFactory()`)
|
|
2806
2818
|
* or build a `RemoteTrackResolver` with custom publisher/subscriber id resolvers.
|
|
2807
2819
|
*/
|
|
2808
|
-
|
|
2820
|
+
createRemoteTrackResolver?: RemoteTrackResolverFactory;
|
|
2809
2821
|
};
|
|
2810
2822
|
declare interface Observer {
|
|
2811
2823
|
on<U extends keyof ObserverEvents>(event: U, listener: (...args: ObserverEvents[U]) => void): this;
|
package/dist/index.d.ts
CHANGED
|
@@ -2232,15 +2232,28 @@ declare interface ObservedMediasoupRouter {
|
|
|
2232
2232
|
once<U extends keyof ObservedMediasoupRouterEvents>(event: U, listener: (...args: ObservedMediasoupRouterEvents[U]) => void): this;
|
|
2233
2233
|
emit<U extends keyof ObservedMediasoupRouterEvents>(event: U, ...args: ObservedMediasoupRouterEvents[U]): boolean;
|
|
2234
2234
|
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Observes a live mediasoup `Router` by subscribing to its `observer` API and **accumulates** its
|
|
2237
|
+
* topology and lifecycle into an in-memory `MediasoupRouterSample` (`observedRouter.sample`):
|
|
2238
|
+
* transports, producers, consumers, data producers/consumers, their state-change history and
|
|
2239
|
+
* `createdAt` / `closedAt`. The sample grows for the life of the router (closed entities are kept,
|
|
2240
|
+
* with their `closedAt` set) and is yours to read, snapshot, or persist.
|
|
2241
|
+
*
|
|
2242
|
+
* NOTE: this is intentionally the simplest approach — everything is held in memory. For very large
|
|
2243
|
+
* routers (e.g. ~100 participants producing and consuming on one router, where consumers grow as
|
|
2244
|
+
* O(N²)) this can become substantial; in that case do your own periodic sampling/persistence and
|
|
2245
|
+
* discard what you don't need (see the README's "Memory & large meetings" note).
|
|
2246
|
+
*/
|
|
2235
2247
|
declare class ObservedMediasoupRouter<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter {
|
|
2236
2248
|
readonly router: types.Router;
|
|
2237
|
-
readonly sample: MediasoupRouterSample;
|
|
2238
2249
|
appData: AppData;
|
|
2250
|
+
readonly sample: MediasoupRouterSample;
|
|
2239
2251
|
readonly webrtcTransportIds: Set<string>;
|
|
2240
|
-
get attachments(): Record<string, unknown>;
|
|
2241
2252
|
closed: boolean;
|
|
2242
2253
|
constructor(settings: ObservedMediasoupRouterSettings<AppData>);
|
|
2243
2254
|
get id(): string;
|
|
2255
|
+
get attachments(): Record<string, unknown>;
|
|
2256
|
+
close(): void;
|
|
2244
2257
|
addTransport: (transport: types.Transport) => void;
|
|
2245
2258
|
addWebRtcTransport(transport: types.WebRtcTransport): void;
|
|
2246
2259
|
addPlainTransport(transport: types.PlainTransport): void;
|
|
@@ -2250,7 +2263,6 @@ declare class ObservedMediasoupRouter<AppData extends Record<string, unknown> =
|
|
|
2250
2263
|
addConsumer(transport: types.Transport, consumer: types.Consumer): void;
|
|
2251
2264
|
addDataProducer(transport: types.Transport, dataProducer: types.DataProducer): void;
|
|
2252
2265
|
addDataConsumer(transport: types.Transport, dataConsumer: types.DataConsumer): void;
|
|
2253
|
-
close(): void;
|
|
2254
2266
|
private attachRouterListeners;
|
|
2255
2267
|
private _attachTransportObserverListeners;
|
|
2256
2268
|
}
|
|
@@ -2805,7 +2817,7 @@ type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unk
|
|
|
2805
2817
|
* (`createDefaultMediasoupRemoteTrackResolverFactory()` / `createP2pRemoteTrackResolverFactory()`)
|
|
2806
2818
|
* or build a `RemoteTrackResolver` with custom publisher/subscriber id resolvers.
|
|
2807
2819
|
*/
|
|
2808
|
-
|
|
2820
|
+
createRemoteTrackResolver?: RemoteTrackResolverFactory;
|
|
2809
2821
|
};
|
|
2810
2822
|
declare interface Observer {
|
|
2811
2823
|
on<U extends keyof ObserverEvents>(event: U, listener: (...args: ObserverEvents[U]) => void): this;
|
package/dist/index.js
CHANGED
|
@@ -3358,12 +3358,9 @@ var import_events5 = require("events");
|
|
|
3358
3358
|
var logger6 = createLogger("ObservedMediasoupRouter");
|
|
3359
3359
|
var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
3360
3360
|
router;
|
|
3361
|
-
sample;
|
|
3362
3361
|
appData;
|
|
3362
|
+
sample;
|
|
3363
3363
|
webrtcTransportIds = /* @__PURE__ */ new Set();
|
|
3364
|
-
get attachments() {
|
|
3365
|
-
return this.sample.attachments;
|
|
3366
|
-
}
|
|
3367
3364
|
closed = false;
|
|
3368
3365
|
constructor(settings) {
|
|
3369
3366
|
super();
|
|
@@ -3384,6 +3381,15 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3384
3381
|
get id() {
|
|
3385
3382
|
return this.router.id;
|
|
3386
3383
|
}
|
|
3384
|
+
get attachments() {
|
|
3385
|
+
return this.sample.attachments;
|
|
3386
|
+
}
|
|
3387
|
+
close() {
|
|
3388
|
+
if (this.closed) return;
|
|
3389
|
+
this.closed = true;
|
|
3390
|
+
if (this.sample.closedAt === void 0) this.sample.closedAt = Date.now();
|
|
3391
|
+
this.emit("close");
|
|
3392
|
+
}
|
|
3387
3393
|
addTransport = (transport) => {
|
|
3388
3394
|
let transportType = transport.type;
|
|
3389
3395
|
if (!transportType) {
|
|
@@ -3416,35 +3422,17 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3416
3422
|
history
|
|
3417
3423
|
};
|
|
3418
3424
|
this.sample.transports.push(transportSample);
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
type: `icestate-changed-to-${iceState}`,
|
|
3422
|
-
timestamp: Date.now()
|
|
3423
|
-
});
|
|
3424
|
-
};
|
|
3425
|
+
this.webrtcTransportIds.add(transport.id);
|
|
3426
|
+
const onIceStateChange = (iceState) => history.push({ type: `icestate-changed-to-${iceState}`, timestamp: Date.now() });
|
|
3425
3427
|
const onIceSelectedTupleChange = (tuple) => {
|
|
3426
3428
|
transportSample.tuple = tuple;
|
|
3427
|
-
history.push({
|
|
3428
|
-
type: "iceselectedtuple-changed",
|
|
3429
|
-
timestamp: Date.now(),
|
|
3430
|
-
...tuple
|
|
3431
|
-
});
|
|
3429
|
+
history.push({ type: "iceselectedtuple-changed", timestamp: Date.now(), ...tuple });
|
|
3432
3430
|
};
|
|
3433
3431
|
const onDtlsStateChange = (dtlsState) => {
|
|
3434
|
-
history.push({
|
|
3435
|
-
|
|
3436
|
-
timestamp: Date.now()
|
|
3437
|
-
});
|
|
3438
|
-
if (dtlsState === "connected" && transportSample.connectedAt === void 0) {
|
|
3439
|
-
transportSample.connectedAt = Date.now();
|
|
3440
|
-
}
|
|
3441
|
-
};
|
|
3442
|
-
const onSctpStateChange = (sctpState) => {
|
|
3443
|
-
history.push({
|
|
3444
|
-
type: `sctpstate-changed-to-${sctpState}`,
|
|
3445
|
-
timestamp: Date.now()
|
|
3446
|
-
});
|
|
3432
|
+
history.push({ type: `dtlsstate-changed-to-${dtlsState}`, timestamp: Date.now() });
|
|
3433
|
+
if (dtlsState === "connected" && transportSample.connectedAt === void 0) transportSample.connectedAt = Date.now();
|
|
3447
3434
|
};
|
|
3435
|
+
const onSctpStateChange = (sctpState) => history.push({ type: `sctpstate-changed-to-${sctpState}`, timestamp: Date.now() });
|
|
3448
3436
|
this._attachTransportObserverListeners(transport, transportSample, () => {
|
|
3449
3437
|
transport.observer.off("icestatechange", onIceStateChange);
|
|
3450
3438
|
transport.observer.off("iceselectedtuplechange", onIceSelectedTupleChange);
|
|
@@ -3456,7 +3444,6 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3456
3444
|
transport.observer.on("iceselectedtuplechange", onIceSelectedTupleChange);
|
|
3457
3445
|
transport.observer.on("dtlsstatechange", onDtlsStateChange);
|
|
3458
3446
|
transport.observer.on("sctpstatechange", onSctpStateChange);
|
|
3459
|
-
this.webrtcTransportIds.add(transport.id);
|
|
3460
3447
|
}
|
|
3461
3448
|
addPlainTransport(transport) {
|
|
3462
3449
|
const history = [];
|
|
@@ -3479,13 +3466,8 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3479
3466
|
history.push({ type: "rtcptuple-changed", timestamp: Date.now(), ...rtcpTuple });
|
|
3480
3467
|
};
|
|
3481
3468
|
const onSctpStateChange = (sctpState) => {
|
|
3482
|
-
if (sctpState === "connected" && transportSample.connectedAt === void 0)
|
|
3483
|
-
|
|
3484
|
-
}
|
|
3485
|
-
history.push({
|
|
3486
|
-
type: `sctpstate-changed-to-${sctpState}`,
|
|
3487
|
-
timestamp: Date.now()
|
|
3488
|
-
});
|
|
3469
|
+
if (sctpState === "connected" && transportSample.connectedAt === void 0) transportSample.connectedAt = Date.now();
|
|
3470
|
+
history.push({ type: `sctpstate-changed-to-${sctpState}`, timestamp: Date.now() });
|
|
3489
3471
|
};
|
|
3490
3472
|
this._attachTransportObserverListeners(transport, transportSample, () => {
|
|
3491
3473
|
transport.observer.off("tuple", onTuple);
|
|
@@ -3507,13 +3489,8 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3507
3489
|
};
|
|
3508
3490
|
this.sample.transports.push(transportSample);
|
|
3509
3491
|
const onSctpStateChange = (sctpState) => {
|
|
3510
|
-
if (sctpState === "connected" && transportSample.connectedAt === void 0)
|
|
3511
|
-
|
|
3512
|
-
}
|
|
3513
|
-
history.push({
|
|
3514
|
-
type: `sctpstate-changed-to-${sctpState}`,
|
|
3515
|
-
timestamp: Date.now()
|
|
3516
|
-
});
|
|
3492
|
+
if (sctpState === "connected" && transportSample.connectedAt === void 0) transportSample.connectedAt = Date.now();
|
|
3493
|
+
history.push({ type: `sctpstate-changed-to-${sctpState}`, timestamp: Date.now() });
|
|
3517
3494
|
};
|
|
3518
3495
|
this._attachTransportObserverListeners(transport, transportSample, () => {
|
|
3519
3496
|
transport.observer.off("sctpstatechange", onSctpStateChange);
|
|
@@ -3526,7 +3503,6 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3526
3503
|
id: transport.id,
|
|
3527
3504
|
type: "direct",
|
|
3528
3505
|
createdAt: now,
|
|
3529
|
-
// A DirectTransport has no network layer — it is connected the instant it exists.
|
|
3530
3506
|
connectedAt: now,
|
|
3531
3507
|
history: []
|
|
3532
3508
|
};
|
|
@@ -3535,37 +3511,26 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3535
3511
|
}
|
|
3536
3512
|
addProducer(transport, producer) {
|
|
3537
3513
|
const firstCodec = producer.rtpParameters.codecs[0];
|
|
3538
|
-
const codecInfo = {
|
|
3539
|
-
mimeType: firstCodec?.mimeType ?? "unknown/unknown",
|
|
3540
|
-
payloadType: firstCodec?.payloadType ?? 0,
|
|
3541
|
-
clockRate: firstCodec?.clockRate ?? 0,
|
|
3542
|
-
channels: firstCodec?.channels,
|
|
3543
|
-
parameters: firstCodec?.parameters,
|
|
3544
|
-
rtcpFeedback: firstCodec?.rtcpFeedback
|
|
3545
|
-
};
|
|
3546
3514
|
const producerSample = {
|
|
3547
3515
|
id: producer.id,
|
|
3516
|
+
transportId: transport.id,
|
|
3548
3517
|
createdAt: Date.now(),
|
|
3549
3518
|
kind: producer.kind,
|
|
3550
|
-
codecInfo
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3519
|
+
codecInfo: {
|
|
3520
|
+
mimeType: firstCodec?.mimeType ?? "unknown/unknown",
|
|
3521
|
+
payloadType: firstCodec?.payloadType ?? 0,
|
|
3522
|
+
clockRate: firstCodec?.clockRate ?? 0,
|
|
3523
|
+
channels: firstCodec?.channels,
|
|
3524
|
+
parameters: firstCodec?.parameters,
|
|
3525
|
+
rtcpFeedback: firstCodec?.rtcpFeedback
|
|
3526
|
+
},
|
|
3527
|
+
ssrcs: producer.rtpParameters.encodings?.map((e) => e.ssrc).filter((s) => typeof s === "number"),
|
|
3528
|
+
rids: producer.rtpParameters.encodings?.map((e) => e.rid).filter((r) => typeof r === "string"),
|
|
3529
|
+
history: []
|
|
3555
3530
|
};
|
|
3556
3531
|
this.sample.producers.push(producerSample);
|
|
3557
|
-
const onPause = () => {
|
|
3558
|
-
|
|
3559
|
-
type: "pause",
|
|
3560
|
-
timestamp: Date.now()
|
|
3561
|
-
});
|
|
3562
|
-
};
|
|
3563
|
-
const onResume = () => {
|
|
3564
|
-
producerSample.history.push({
|
|
3565
|
-
type: "resume",
|
|
3566
|
-
timestamp: Date.now()
|
|
3567
|
-
});
|
|
3568
|
-
};
|
|
3532
|
+
const onPause = () => producerSample.history.push({ type: "pause", timestamp: Date.now() });
|
|
3533
|
+
const onResume = () => producerSample.history.push({ type: "resume", timestamp: Date.now() });
|
|
3569
3534
|
producer.observer.once("close", () => {
|
|
3570
3535
|
producer.observer.off("pause", onPause);
|
|
3571
3536
|
producer.observer.off("resume", onResume);
|
|
@@ -3584,30 +3549,10 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3584
3549
|
history: []
|
|
3585
3550
|
};
|
|
3586
3551
|
this.sample.consumers.push(consumerSample);
|
|
3587
|
-
const onPause = () => {
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
});
|
|
3592
|
-
};
|
|
3593
|
-
const onResume = () => {
|
|
3594
|
-
consumerSample.history.push({
|
|
3595
|
-
type: "resume",
|
|
3596
|
-
timestamp: Date.now()
|
|
3597
|
-
});
|
|
3598
|
-
};
|
|
3599
|
-
const onProducerPause = () => {
|
|
3600
|
-
consumerSample.history.push({
|
|
3601
|
-
type: "producerPaused",
|
|
3602
|
-
timestamp: Date.now()
|
|
3603
|
-
});
|
|
3604
|
-
};
|
|
3605
|
-
const onProducerResume = () => {
|
|
3606
|
-
consumerSample.history.push({
|
|
3607
|
-
type: "producerResumed",
|
|
3608
|
-
timestamp: Date.now()
|
|
3609
|
-
});
|
|
3610
|
-
};
|
|
3552
|
+
const onPause = () => consumerSample.history.push({ type: "pause", timestamp: Date.now() });
|
|
3553
|
+
const onResume = () => consumerSample.history.push({ type: "resume", timestamp: Date.now() });
|
|
3554
|
+
const onProducerPause = () => consumerSample.history.push({ type: "producerPaused", timestamp: Date.now() });
|
|
3555
|
+
const onProducerResume = () => consumerSample.history.push({ type: "producerResumed", timestamp: Date.now() });
|
|
3611
3556
|
consumer.observer.once("close", () => {
|
|
3612
3557
|
consumer.observer.off("pause", onPause);
|
|
3613
3558
|
consumer.observer.off("resume", onResume);
|
|
@@ -3629,9 +3574,7 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3629
3574
|
protocol: dataProducer.protocol
|
|
3630
3575
|
};
|
|
3631
3576
|
this.sample.dataProducers.push(dataProducerSample);
|
|
3632
|
-
dataProducer.observer.once("close", () =>
|
|
3633
|
-
dataProducerSample.closedAt = Date.now();
|
|
3634
|
-
});
|
|
3577
|
+
dataProducer.observer.once("close", () => dataProducerSample.closedAt = Date.now());
|
|
3635
3578
|
}
|
|
3636
3579
|
addDataConsumer(transport, dataConsumer) {
|
|
3637
3580
|
const dataConsumerSample = {
|
|
@@ -3643,14 +3586,7 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3643
3586
|
protocol: dataConsumer.protocol
|
|
3644
3587
|
};
|
|
3645
3588
|
this.sample.dataConsumers.push(dataConsumerSample);
|
|
3646
|
-
dataConsumer.observer.once("close", () =>
|
|
3647
|
-
dataConsumerSample.closedAt = Date.now();
|
|
3648
|
-
});
|
|
3649
|
-
}
|
|
3650
|
-
close() {
|
|
3651
|
-
if (this.closed) return;
|
|
3652
|
-
this.closed = true;
|
|
3653
|
-
this.emit("close");
|
|
3589
|
+
dataConsumer.observer.once("close", () => dataConsumerSample.closedAt = Date.now());
|
|
3654
3590
|
}
|
|
3655
3591
|
attachRouterListeners() {
|
|
3656
3592
|
this.router.observer.once("close", () => {
|
|
@@ -3661,18 +3597,10 @@ var ObservedMediasoupRouter = class extends import_events5.EventEmitter {
|
|
|
3661
3597
|
this.router.observer.on("newtransport", this.addTransport);
|
|
3662
3598
|
}
|
|
3663
3599
|
_attachTransportObserverListeners(transport, transportSample, onClose) {
|
|
3664
|
-
const onNewProducer = (producer) =>
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
const
|
|
3668
|
-
this.addConsumer(transport, consumer);
|
|
3669
|
-
};
|
|
3670
|
-
const onNewDataProducer = (dataProducer) => {
|
|
3671
|
-
this.addDataProducer(transport, dataProducer);
|
|
3672
|
-
};
|
|
3673
|
-
const onNewDataConsumer = (dataConsumer) => {
|
|
3674
|
-
this.addDataConsumer(transport, dataConsumer);
|
|
3675
|
-
};
|
|
3600
|
+
const onNewProducer = (producer) => this.addProducer(transport, producer);
|
|
3601
|
+
const onNewConsumer = (consumer) => this.addConsumer(transport, consumer);
|
|
3602
|
+
const onNewDataProducer = (dataProducer) => this.addDataProducer(transport, dataProducer);
|
|
3603
|
+
const onNewDataConsumer = (dataConsumer) => this.addDataConsumer(transport, dataConsumer);
|
|
3676
3604
|
transport.observer.once("close", () => {
|
|
3677
3605
|
transport.observer.off("newproducer", onNewProducer);
|
|
3678
3606
|
transport.observer.off("newconsumer", onNewConsumer);
|
|
@@ -3755,7 +3683,7 @@ var Observer = class extends import_events6.EventEmitter {
|
|
|
3755
3683
|
appData: settings.appData ?? this.config.createCallAppData?.({ callId: settings.callId, observer: this })
|
|
3756
3684
|
};
|
|
3757
3685
|
const observedCall = new ObservedCall(callSettings, this);
|
|
3758
|
-
observedCall.remoteTrackResolver = this.config.
|
|
3686
|
+
observedCall.remoteTrackResolver = this.config.createRemoteTrackResolver?.(observedCall);
|
|
3759
3687
|
observedCall.once("close", () => {
|
|
3760
3688
|
this.observedCalls.delete(observedCall.callId);
|
|
3761
3689
|
++this.totalRemovedCall;
|