@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/dist/index.mjs CHANGED
@@ -37,8 +37,8 @@ function createLogger(moduleName) {
37
37
  }
38
38
  }();
39
39
  }
40
- function setObserverLogger(logger7) {
41
- mainLogger = logger7;
40
+ function setObserverLogger(logger8) {
41
+ mainLogger = logger8;
42
42
  }
43
43
 
44
44
  // src/ObservedCall.ts
@@ -2985,7 +2985,7 @@ var ObservedCall = class extends EventEmitter3 {
2985
2985
  };
2986
2986
 
2987
2987
  // src/Observer.ts
2988
- import { EventEmitter as EventEmitter5 } from "events";
2988
+ import { EventEmitter as EventEmitter6 } from "events";
2989
2989
 
2990
2990
  // src/ObservedTURN.ts
2991
2991
  import { EventEmitter as EventEmitter4 } from "events";
@@ -3231,9 +3231,344 @@ var MiddlewareProcessor = class {
3231
3231
  }
3232
3232
  };
3233
3233
 
3234
+ // src/ObservedMediasoupRouter.ts
3235
+ import { EventEmitter as EventEmitter5 } from "events";
3236
+ var logger6 = createLogger("ObservedMediasoupRouter");
3237
+ var ObservedMediasoupRouter = class extends EventEmitter5 {
3238
+ router;
3239
+ sample;
3240
+ appData;
3241
+ webrtcTransportIds = /* @__PURE__ */ new Set();
3242
+ get attachments() {
3243
+ return this.sample.attachments;
3244
+ }
3245
+ closed = false;
3246
+ constructor(settings) {
3247
+ super();
3248
+ this.router = settings.router;
3249
+ this.appData = settings.appData ?? {};
3250
+ this.sample = {
3251
+ routerId: this.router.id,
3252
+ attachments: settings.attachments ?? {},
3253
+ createdAt: Date.now(),
3254
+ producers: [],
3255
+ consumers: [],
3256
+ dataProducers: [],
3257
+ dataConsumers: [],
3258
+ transports: []
3259
+ };
3260
+ this.attachRouterListeners();
3261
+ }
3262
+ get id() {
3263
+ return this.router.id;
3264
+ }
3265
+ addTransport = (transport) => {
3266
+ let transportType = transport.type;
3267
+ if (!transportType) {
3268
+ if (transport.constructor.name === "WebRtcTransport") transportType = "webrtc";
3269
+ else if (transport.constructor.name === "PlainTransport") transportType = "plain";
3270
+ else if (transport.constructor.name === "PipeTransport") transportType = "pipe";
3271
+ else if (transport.constructor.name === "DirectTransport") transportType = "direct";
3272
+ }
3273
+ if (!transportType) return logger6.warn(`Unknown transport type for transport ${transport.id}: ${transport.constructor.name}`);
3274
+ switch (transportType) {
3275
+ case "webrtc":
3276
+ return this.addWebRtcTransport(transport);
3277
+ case "plain":
3278
+ return this.addPlainTransport(transport);
3279
+ case "pipe":
3280
+ return this.addPipeTransport(transport);
3281
+ case "direct":
3282
+ return this.addDirectTransport(transport);
3283
+ default:
3284
+ return logger6.warn(`Unsupported transport type for transport ${transport.id}: ${transportType}`);
3285
+ }
3286
+ };
3287
+ addWebRtcTransport(transport) {
3288
+ const history = [];
3289
+ const transportSample = {
3290
+ id: transport.id,
3291
+ type: "webrtc",
3292
+ createdAt: Date.now(),
3293
+ tuple: transport.iceSelectedTuple,
3294
+ history
3295
+ };
3296
+ this.sample.transports.push(transportSample);
3297
+ const onIceStateChange = (iceState) => {
3298
+ history.push({
3299
+ type: `icestate-changed-to-${iceState}`,
3300
+ timestamp: Date.now()
3301
+ });
3302
+ };
3303
+ const onIceSelectedTupleChange = (tuple) => {
3304
+ transportSample.tuple = tuple;
3305
+ history.push({
3306
+ type: "iceselectedtuple-changed",
3307
+ timestamp: Date.now(),
3308
+ ...tuple
3309
+ });
3310
+ };
3311
+ const onDtlsStateChange = (dtlsState) => {
3312
+ history.push({
3313
+ type: `dtlsstate-changed-to-${dtlsState}`,
3314
+ timestamp: Date.now()
3315
+ });
3316
+ if (dtlsState === "connected" && transportSample.connectedAt === void 0) {
3317
+ transportSample.connectedAt = Date.now();
3318
+ }
3319
+ };
3320
+ const onSctpStateChange = (sctpState) => {
3321
+ history.push({
3322
+ type: `sctpstate-changed-to-${sctpState}`,
3323
+ timestamp: Date.now()
3324
+ });
3325
+ };
3326
+ this._attachTransportObserverListeners(transport, transportSample, () => {
3327
+ transport.observer.off("icestatechange", onIceStateChange);
3328
+ transport.observer.off("iceselectedtuplechange", onIceSelectedTupleChange);
3329
+ transport.observer.off("dtlsstatechange", onDtlsStateChange);
3330
+ transport.observer.off("sctpstatechange", onSctpStateChange);
3331
+ this.webrtcTransportIds.delete(transport.id);
3332
+ });
3333
+ transport.observer.on("icestatechange", onIceStateChange);
3334
+ transport.observer.on("iceselectedtuplechange", onIceSelectedTupleChange);
3335
+ transport.observer.on("dtlsstatechange", onDtlsStateChange);
3336
+ transport.observer.on("sctpstatechange", onSctpStateChange);
3337
+ this.webrtcTransportIds.add(transport.id);
3338
+ }
3339
+ addPlainTransport(transport) {
3340
+ const history = [];
3341
+ const transportSample = {
3342
+ id: transport.id,
3343
+ type: "plain",
3344
+ createdAt: Date.now(),
3345
+ tuple: transport.tuple,
3346
+ rtcpTuple: transport.rtcpTuple,
3347
+ history
3348
+ };
3349
+ this.sample.transports.push(transportSample);
3350
+ const onTuple = (tuple) => {
3351
+ transportSample.tuple = tuple;
3352
+ if (transportSample.connectedAt === void 0) transportSample.connectedAt = Date.now();
3353
+ history.push({ type: "tuple-changed", timestamp: Date.now(), ...tuple });
3354
+ };
3355
+ const onRtcpTuple = (rtcpTuple) => {
3356
+ transportSample.rtcpTuple = rtcpTuple;
3357
+ history.push({ type: "rtcptuple-changed", timestamp: Date.now(), ...rtcpTuple });
3358
+ };
3359
+ const onSctpStateChange = (sctpState) => {
3360
+ if (sctpState === "connected" && transportSample.connectedAt === void 0) {
3361
+ transportSample.connectedAt = Date.now();
3362
+ }
3363
+ history.push({
3364
+ type: `sctpstate-changed-to-${sctpState}`,
3365
+ timestamp: Date.now()
3366
+ });
3367
+ };
3368
+ this._attachTransportObserverListeners(transport, transportSample, () => {
3369
+ transport.observer.off("tuple", onTuple);
3370
+ transport.observer.off("rtcptuple", onRtcpTuple);
3371
+ transport.observer.off("sctpstatechange", onSctpStateChange);
3372
+ });
3373
+ transport.observer.on("tuple", onTuple);
3374
+ transport.observer.on("rtcptuple", onRtcpTuple);
3375
+ transport.observer.on("sctpstatechange", onSctpStateChange);
3376
+ }
3377
+ addPipeTransport(transport) {
3378
+ const history = [];
3379
+ const transportSample = {
3380
+ id: transport.id,
3381
+ type: "pipe",
3382
+ createdAt: Date.now(),
3383
+ tuple: transport.tuple,
3384
+ history
3385
+ };
3386
+ this.sample.transports.push(transportSample);
3387
+ const onSctpStateChange = (sctpState) => {
3388
+ if (sctpState === "connected" && transportSample.connectedAt === void 0) {
3389
+ transportSample.connectedAt = Date.now();
3390
+ }
3391
+ history.push({
3392
+ type: `sctpstate-changed-to-${sctpState}`,
3393
+ timestamp: Date.now()
3394
+ });
3395
+ };
3396
+ this._attachTransportObserverListeners(transport, transportSample, () => {
3397
+ transport.observer.off("sctpstatechange", onSctpStateChange);
3398
+ });
3399
+ transport.observer.on("sctpstatechange", onSctpStateChange);
3400
+ }
3401
+ addDirectTransport(transport) {
3402
+ const now = Date.now();
3403
+ const transportSample = {
3404
+ id: transport.id,
3405
+ type: "direct",
3406
+ createdAt: now,
3407
+ // A DirectTransport has no network layer — it is connected the instant it exists.
3408
+ connectedAt: now,
3409
+ history: []
3410
+ };
3411
+ this.sample.transports.push(transportSample);
3412
+ this._attachTransportObserverListeners(transport, transportSample);
3413
+ }
3414
+ addProducer(transport, producer) {
3415
+ const firstCodec = producer.rtpParameters.codecs[0];
3416
+ const codecInfo = {
3417
+ mimeType: firstCodec?.mimeType ?? "unknown/unknown",
3418
+ payloadType: firstCodec?.payloadType ?? 0,
3419
+ clockRate: firstCodec?.clockRate ?? 0,
3420
+ channels: firstCodec?.channels,
3421
+ parameters: firstCodec?.parameters,
3422
+ rtcpFeedback: firstCodec?.rtcpFeedback
3423
+ };
3424
+ const producerSample = {
3425
+ id: producer.id,
3426
+ createdAt: Date.now(),
3427
+ kind: producer.kind,
3428
+ codecInfo,
3429
+ ssrcs: producer.rtpParameters.encodings?.map((encoding) => encoding.ssrc).filter((ssrc) => typeof ssrc === "number"),
3430
+ rids: producer.rtpParameters.encodings?.map((encoding) => encoding.rid).filter((rid) => typeof rid === "string"),
3431
+ history: [],
3432
+ transportId: transport.id
3433
+ };
3434
+ this.sample.producers.push(producerSample);
3435
+ const onPause = () => {
3436
+ producerSample.history.push({
3437
+ type: "pause",
3438
+ timestamp: Date.now()
3439
+ });
3440
+ };
3441
+ const onResume = () => {
3442
+ producerSample.history.push({
3443
+ type: "resume",
3444
+ timestamp: Date.now()
3445
+ });
3446
+ };
3447
+ producer.observer.once("close", () => {
3448
+ producer.observer.off("pause", onPause);
3449
+ producer.observer.off("resume", onResume);
3450
+ producerSample.closedAt = Date.now();
3451
+ });
3452
+ producer.observer.on("pause", onPause);
3453
+ producer.observer.on("resume", onResume);
3454
+ }
3455
+ addConsumer(transport, consumer) {
3456
+ const consumerSample = {
3457
+ id: consumer.id,
3458
+ producerId: consumer.producerId,
3459
+ transportId: transport.id,
3460
+ createdAt: Date.now(),
3461
+ kind: consumer.kind,
3462
+ history: []
3463
+ };
3464
+ this.sample.consumers.push(consumerSample);
3465
+ const onPause = () => {
3466
+ consumerSample.history.push({
3467
+ type: "pause",
3468
+ timestamp: Date.now()
3469
+ });
3470
+ };
3471
+ const onResume = () => {
3472
+ consumerSample.history.push({
3473
+ type: "resume",
3474
+ timestamp: Date.now()
3475
+ });
3476
+ };
3477
+ const onProducerPause = () => {
3478
+ consumerSample.history.push({
3479
+ type: "producerPaused",
3480
+ timestamp: Date.now()
3481
+ });
3482
+ };
3483
+ const onProducerResume = () => {
3484
+ consumerSample.history.push({
3485
+ type: "producerResumed",
3486
+ timestamp: Date.now()
3487
+ });
3488
+ };
3489
+ consumer.observer.once("close", () => {
3490
+ consumer.observer.off("pause", onPause);
3491
+ consumer.observer.off("resume", onResume);
3492
+ consumer.off("producerresume", onProducerResume);
3493
+ consumer.off("producerpause", onProducerPause);
3494
+ consumerSample.closedAt = Date.now();
3495
+ });
3496
+ consumer.observer.on("pause", onPause);
3497
+ consumer.observer.on("resume", onResume);
3498
+ consumer.on("producerpause", onProducerPause);
3499
+ consumer.on("producerresume", onProducerResume);
3500
+ }
3501
+ addDataProducer(transport, dataProducer) {
3502
+ const dataProducerSample = {
3503
+ id: dataProducer.id,
3504
+ transportId: transport.id,
3505
+ createdAt: Date.now(),
3506
+ label: dataProducer.label,
3507
+ protocol: dataProducer.protocol
3508
+ };
3509
+ this.sample.dataProducers.push(dataProducerSample);
3510
+ dataProducer.observer.once("close", () => {
3511
+ dataProducerSample.closedAt = Date.now();
3512
+ });
3513
+ }
3514
+ addDataConsumer(transport, dataConsumer) {
3515
+ const dataConsumerSample = {
3516
+ id: dataConsumer.id,
3517
+ dataProducerId: dataConsumer.dataProducerId,
3518
+ transportId: transport.id,
3519
+ createdAt: Date.now(),
3520
+ label: dataConsumer.label,
3521
+ protocol: dataConsumer.protocol
3522
+ };
3523
+ this.sample.dataConsumers.push(dataConsumerSample);
3524
+ dataConsumer.observer.once("close", () => {
3525
+ dataConsumerSample.closedAt = Date.now();
3526
+ });
3527
+ }
3528
+ close() {
3529
+ if (this.closed) return;
3530
+ this.closed = true;
3531
+ this.emit("close");
3532
+ }
3533
+ attachRouterListeners() {
3534
+ this.router.observer.once("close", () => {
3535
+ this.router.observer.off("newtransport", this.addTransport);
3536
+ this.sample.closedAt = Date.now();
3537
+ this.close();
3538
+ });
3539
+ this.router.observer.on("newtransport", this.addTransport);
3540
+ }
3541
+ _attachTransportObserverListeners(transport, transportSample, onClose) {
3542
+ const onNewProducer = (producer) => {
3543
+ this.addProducer(transport, producer);
3544
+ };
3545
+ const onNewConsumer = (consumer) => {
3546
+ this.addConsumer(transport, consumer);
3547
+ };
3548
+ const onNewDataProducer = (dataProducer) => {
3549
+ this.addDataProducer(transport, dataProducer);
3550
+ };
3551
+ const onNewDataConsumer = (dataConsumer) => {
3552
+ this.addDataConsumer(transport, dataConsumer);
3553
+ };
3554
+ transport.observer.once("close", () => {
3555
+ transport.observer.off("newproducer", onNewProducer);
3556
+ transport.observer.off("newconsumer", onNewConsumer);
3557
+ transport.observer.off("newdataproducer", onNewDataProducer);
3558
+ transport.observer.off("newdataconsumer", onNewDataConsumer);
3559
+ onClose?.();
3560
+ transportSample.closedAt = Date.now();
3561
+ });
3562
+ transport.observer.on("newproducer", onNewProducer);
3563
+ transport.observer.on("newconsumer", onNewConsumer);
3564
+ transport.observer.on("newdataproducer", onNewDataProducer);
3565
+ transport.observer.on("newdataconsumer", onNewDataConsumer);
3566
+ }
3567
+ };
3568
+
3234
3569
  // src/Observer.ts
3235
- var logger6 = createLogger("Observer");
3236
- var Observer = class extends EventEmitter5 {
3570
+ var logger7 = createLogger("Observer");
3571
+ var Observer = class extends EventEmitter6 {
3237
3572
  constructor(config = {
3238
3573
  updatePolicy: "update-when-all-call-updated",
3239
3574
  appData: {}
@@ -3257,6 +3592,7 @@ var Observer = class extends EventEmitter5 {
3257
3592
  config;
3258
3593
  observedTURN = new ObservedTURN();
3259
3594
  observedCalls = /* @__PURE__ */ new Map();
3595
+ observedMediasoupRouters = /* @__PURE__ */ new Map();
3260
3596
  updater;
3261
3597
  /** Ancestry base shared by all Observer-bus events originating at the observer. */
3262
3598
  eventScope = { observer: this };
@@ -3283,11 +3619,11 @@ var Observer = class extends EventEmitter5 {
3283
3619
  }
3284
3620
  createObservedCall(settings) {
3285
3621
  if (this.closed) {
3286
- logger6.warn("Attempted to create a call (callId: %s) on a closed observer", settings.callId);
3622
+ logger7.warn("Attempted to create a call (callId: %s) on a closed observer", settings.callId);
3287
3623
  return void 0;
3288
3624
  }
3289
3625
  if (this.observedCalls.has(settings.callId)) {
3290
- logger6.warn("Observed Call with id %s already exists; returning the existing instance", settings.callId);
3626
+ logger7.warn("Observed Call with id %s already exists; returning the existing instance", settings.callId);
3291
3627
  return this.observedCalls.get(settings.callId);
3292
3628
  }
3293
3629
  const callSettings = {
@@ -3310,9 +3646,53 @@ var Observer = class extends EventEmitter5 {
3310
3646
  getOrCreateObservedCall(settings) {
3311
3647
  return this.getObservedCall(settings.callId) ?? this.createObservedCall(settings);
3312
3648
  }
3649
+ createObservedMediasoupRouter(settings) {
3650
+ if (this.closed) {
3651
+ logger7.warn("Attempted to create mediasoup router (id: %d) on a closed observer", settings.router.id);
3652
+ return void 0;
3653
+ }
3654
+ if (this.observedMediasoupRouters.has(settings.router.id)) {
3655
+ logger7.warn("Observed Mediasoup Router (id %s) already exists; returning the existing instance", settings.router.id);
3656
+ return this.observedMediasoupRouters.get(settings.router.id);
3657
+ }
3658
+ const observedMediasoupRouter = new ObservedMediasoupRouter(settings);
3659
+ const observedMediasoupRouterScope = {
3660
+ observedMediasoupRouter,
3661
+ observer: this
3662
+ };
3663
+ const directCall = settings.callId ? this.observedCalls.get(settings.callId) : void 0;
3664
+ if (directCall) {
3665
+ this.emit("mediasoup-router-matched-with-call", {
3666
+ ...observedMediasoupRouterScope,
3667
+ observedCall: directCall
3668
+ });
3669
+ } else if (settings.bindCallByWebRtcTransportId) {
3670
+ const matchedCallIds = /* @__PURE__ */ new Set();
3671
+ const onPeerConnectionAdded = ({ observedPeerConnection, observedCall }) => {
3672
+ if (!observedMediasoupRouter.webrtcTransportIds.has(observedPeerConnection.peerConnectionId)) return;
3673
+ if (matchedCallIds.has(observedCall.callId)) return;
3674
+ matchedCallIds.add(observedCall.callId);
3675
+ this.emit("mediasoup-router-matched-with-call", {
3676
+ ...observedMediasoupRouterScope,
3677
+ observedCall
3678
+ });
3679
+ };
3680
+ const stopMatching = () => this.off("peer-connection-added", onPeerConnectionAdded);
3681
+ this.on("peer-connection-added", onPeerConnectionAdded);
3682
+ this.once("observer-closed", stopMatching);
3683
+ observedMediasoupRouter.once("close", stopMatching);
3684
+ }
3685
+ observedMediasoupRouter.once("close", () => {
3686
+ this.observedMediasoupRouters.delete(observedMediasoupRouter.id);
3687
+ this.emit("mediasoup-router-removed", observedMediasoupRouterScope);
3688
+ });
3689
+ this.observedMediasoupRouters.set(observedMediasoupRouter.id, observedMediasoupRouter);
3690
+ this.emit("mediasoup-router-added", observedMediasoupRouterScope);
3691
+ return observedMediasoupRouter;
3692
+ }
3313
3693
  close() {
3314
3694
  if (this.closed) {
3315
- return logger6.debug("Attempted to close twice");
3695
+ return logger7.debug("Attempted to close twice");
3316
3696
  }
3317
3697
  this.closed = true;
3318
3698
  this.observedCalls.forEach((call) => call.close());
@@ -3325,7 +3705,7 @@ var Observer = class extends EventEmitter5 {
3325
3705
  try {
3326
3706
  this.acceptMiddlewares.process({ sample, context });
3327
3707
  } catch (err) {
3328
- logger6.warn("An accept middleware threw; dropping the sample. %o", err);
3708
+ logger7.warn("An accept middleware threw; dropping the sample. %o", err);
3329
3709
  }
3330
3710
  if (!sample.callId) {
3331
3711
  this._notify("sample-rejected", { ...this.eventScope, reason: "missing-callId", sample });
@@ -3381,8 +3761,8 @@ var Observer = class extends EventEmitter5 {
3381
3761
  };
3382
3762
 
3383
3763
  // src/sinks/ClientSampleSink.ts
3384
- import { EventEmitter as EventEmitter6 } from "events";
3385
- var ClientSampleSink = class extends EventEmitter6 {
3764
+ import { EventEmitter as EventEmitter7 } from "events";
3765
+ var ClientSampleSink = class extends EventEmitter7 {
3386
3766
  };
3387
3767
 
3388
3768
  // src/sinks/JsonlFileSink.ts
@@ -3581,6 +3961,7 @@ export {
3581
3961
  ObservedInboundTrack,
3582
3962
  ObservedMediaPlayout,
3583
3963
  ObservedMediaSource,
3964
+ ObservedMediasoupRouter,
3584
3965
  ObservedOutboundRtp,
3585
3966
  ObservedOutboundTrack,
3586
3967
  ObservedPeerConnection,