@observertc/observer-js 1.3.0-rc.4 → 1.3.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1778,19 +1778,17 @@ function isPublicIpAddress(address) {
1778
1778
 
1779
1779
  // src/ObservedOutboundTrack.ts
1780
1780
  var ObservedOutboundTrack = class {
1781
- constructor(timestamp, id, kind, _peerConnection, _outboundRtps, _mediaSource) {
1781
+ constructor(timestamp, id, kind, _peerConnection, _mediaSource) {
1782
1782
  this.timestamp = timestamp;
1783
1783
  this.id = id;
1784
1784
  this.kind = kind;
1785
1785
  this._peerConnection = _peerConnection;
1786
- this._outboundRtps = _outboundRtps;
1787
1786
  this._mediaSource = _mediaSource;
1788
1787
  }
1789
1788
  timestamp;
1790
1789
  id;
1791
1790
  kind;
1792
1791
  _peerConnection;
1793
- _outboundRtps;
1794
1792
  _mediaSource;
1795
1793
  _visited = false;
1796
1794
  appData;
@@ -1841,8 +1839,11 @@ var ObservedOutboundTrack = class {
1841
1839
  getPeerConnection() {
1842
1840
  return this._peerConnection;
1843
1841
  }
1842
+ /** The outbound RTPs of this track's media source, looked up on every call. */
1844
1843
  getOutboundRtps() {
1845
- return this._outboundRtps;
1844
+ const mediaSource = this._mediaSource;
1845
+ if (!mediaSource) return void 0;
1846
+ return [...this._peerConnection.observedOutboundRtps.values()].filter((rtp) => rtp.mediaSourceId === mediaSource.id);
1846
1847
  }
1847
1848
  getMediaSource() {
1848
1849
  return this._mediaSource;
@@ -1861,7 +1862,7 @@ var ObservedOutboundTrack = class {
1861
1862
  this.remoteFractionLost = void 0;
1862
1863
  this.remoteRttInMs = void 0;
1863
1864
  this.qualityLimitationReason = void 0;
1864
- for (const rtp of this._outboundRtps ?? []) {
1865
+ for (const rtp of this.getOutboundRtps() ?? []) {
1865
1866
  this.bitrate = (this.bitrate ?? 0) + rtp.bitrate;
1866
1867
  this.deltaPacketsSent = (this.deltaPacketsSent ?? 0) + rtp.deltaPacketsSent;
1867
1868
  if (rtp.remoteFractionLost !== void 0) {
@@ -2526,9 +2527,7 @@ var ObservedPeerConnection = class extends EventEmitter2 {
2526
2527
  }
2527
2528
  for (const observedOutboundTrack of [...this.observedOutboundTracks.values()]) {
2528
2529
  if (observedOutboundTrack.visited) continue;
2529
- this.observedOutboundTracks.delete(observedOutboundTrack.id);
2530
- this.emit("removed-outbound-track", observedOutboundTrack);
2531
- this._notify("outbound-track-removed", { ...this.eventScope, observedOutboundTrack });
2530
+ this._removeOutboundTrack(observedOutboundTrack);
2532
2531
  }
2533
2532
  for (const observedPeerConnectionTransport of [...this.observedPeerConnectionTransports.values()]) {
2534
2533
  if (observedPeerConnectionTransport.visited) continue;
@@ -2789,6 +2788,11 @@ var ObservedPeerConnection = class extends EventEmitter2 {
2789
2788
  }
2790
2789
  _updateOutboundTrackSample(stats) {
2791
2790
  let observedOutboundTrack = this.observedOutboundTracks.get(stats.id);
2791
+ const mediaSource = [...this.observedMediaSources.values()].filter((source) => source.trackIdentifier === stats.id).pop();
2792
+ if (observedOutboundTrack && observedOutboundTrack.getMediaSource() !== mediaSource) {
2793
+ this._removeOutboundTrack(observedOutboundTrack);
2794
+ observedOutboundTrack = void 0;
2795
+ }
2792
2796
  if (!observedOutboundTrack) {
2793
2797
  if (!stats.timestamp || !stats.id || !stats.kind) {
2794
2798
  return logger3.warn(
@@ -2796,15 +2800,12 @@ var ObservedPeerConnection = class extends EventEmitter2 {
2796
2800
  stats
2797
2801
  );
2798
2802
  }
2799
- const observedMediaSource = [...this.observedMediaSources.values()].find((mediaSource) => mediaSource.trackIdentifier === stats.id);
2800
- const outboundRtps = observedMediaSource ? [...this.observedOutboundRtps.values()].filter((outboundRtp) => outboundRtp.mediaSourceId === observedMediaSource?.id) : void 0;
2801
2803
  observedOutboundTrack = new ObservedOutboundTrack(
2802
2804
  stats.timestamp,
2803
2805
  stats.id,
2804
2806
  stats.kind,
2805
2807
  this,
2806
- outboundRtps,
2807
- observedMediaSource
2808
+ mediaSource
2808
2809
  );
2809
2810
  observedOutboundTrack.update(stats);
2810
2811
  this.observedOutboundTracks.set(stats.id, observedOutboundTrack);
@@ -2815,6 +2816,11 @@ var ObservedPeerConnection = class extends EventEmitter2 {
2815
2816
  this._notify("outbound-track-updated", { ...this.eventScope, observedOutboundTrack });
2816
2817
  return observedOutboundTrack;
2817
2818
  }
2819
+ _removeOutboundTrack(observedOutboundTrack) {
2820
+ this.observedOutboundTracks.delete(observedOutboundTrack.id);
2821
+ this.emit("removed-outbound-track", observedOutboundTrack);
2822
+ this._notify("outbound-track-removed", { ...this.eventScope, observedOutboundTrack });
2823
+ }
2818
2824
  _updatePeerConnectionTransportStats(stats) {
2819
2825
  let observedPeerConnectionTransport = this.observedPeerConnectionTransports.get(stats.id);
2820
2826
  if (!observedPeerConnectionTransport) {
@@ -7979,9 +7985,96 @@ var TrendTester = class {
7979
7985
  }
7980
7986
  };
7981
7987
 
7982
- // src/sinks/InMemorySink.ts
7988
+ // src/sinks/RollingJsonlFileSink.ts
7989
+ import * as fs2 from "fs";
7983
7990
  import { EventEmitter as EventEmitter8 } from "events";
7984
- var InMemorySink = class extends EventEmitter8 {
7991
+ var RollingJsonlFileSink = class extends EventEmitter8 {
7992
+ id;
7993
+ _options;
7994
+ _serializeSample;
7995
+ _stream;
7996
+ _path;
7997
+ _index = -1;
7998
+ _openedAt = 0;
7999
+ _bytes = 0;
8000
+ _ended = false;
8001
+ // Resolves once every file closed so far has closed and been announced, in index order: the
8002
+ // streams close independently, so a later file could otherwise be announced before an earlier one.
8003
+ _closing = Promise.resolve();
8004
+ constructor(options) {
8005
+ super();
8006
+ this._options = options;
8007
+ this._serializeSample = options.serializeSample ?? ((record) => JSON.stringify(record));
8008
+ this._open();
8009
+ this.id = options.id ?? this._path;
8010
+ }
8011
+ /** The file currently written to. */
8012
+ get path() {
8013
+ return this._path;
8014
+ }
8015
+ /** The index of the file currently written to. */
8016
+ get index() {
8017
+ return this._index;
8018
+ }
8019
+ get ended() {
8020
+ return this._ended;
8021
+ }
8022
+ write(record) {
8023
+ if (this._ended) return false;
8024
+ const line = `${this._serializeSample(record)}
8025
+ `;
8026
+ const bytes = Buffer.byteLength(line);
8027
+ const { maxBytes, maxAgeMs } = this._options;
8028
+ if (maxAgeMs !== void 0 && maxAgeMs <= Date.now() - this._openedAt) this.roll();
8029
+ if (maxBytes !== void 0 && maxBytes < this._bytes + bytes) this.roll();
8030
+ this._bytes += bytes;
8031
+ return this._stream.write(line);
8032
+ }
8033
+ /** Close the current file and continue in a new one. A no-op on an empty file or an ended sink. */
8034
+ roll() {
8035
+ if (this._ended || this._bytes === 0) return;
8036
+ this._closeCurrent();
8037
+ this._open();
8038
+ }
8039
+ /** No more writes: emits `ended`, closes the current file (`file-closed`), then emits `close`. */
8040
+ end() {
8041
+ if (this._ended) return;
8042
+ this._ended = true;
8043
+ this.emit("ended");
8044
+ this._closeCurrent();
8045
+ void this._closing.then(() => {
8046
+ this.emit("finish");
8047
+ this.emit("close");
8048
+ });
8049
+ }
8050
+ _open() {
8051
+ ++this._index;
8052
+ this._openedAt = Date.now();
8053
+ this._bytes = 0;
8054
+ this._path = this._options.path({ index: this._index, openedAt: this._openedAt });
8055
+ const stream = fs2.createWriteStream(this._path, { flags: "a" });
8056
+ stream.on("drain", () => this.emit("drain"));
8057
+ stream.on("error", (err) => this.emit("error", err));
8058
+ this._stream = stream;
8059
+ }
8060
+ _closeCurrent() {
8061
+ const stream = this._stream;
8062
+ const path = this._path;
8063
+ const index = this._index;
8064
+ const closed = new Promise((resolve) => {
8065
+ stream.once("close", () => resolve());
8066
+ stream.once("error", () => resolve());
8067
+ });
8068
+ this._closing = Promise.all([this._closing, closed]).then(() => {
8069
+ this.emit("file-closed", path, index);
8070
+ });
8071
+ stream.end();
8072
+ }
8073
+ };
8074
+
8075
+ // src/sinks/InMemorySink.ts
8076
+ import { EventEmitter as EventEmitter9 } from "events";
8077
+ var InMemorySink = class extends EventEmitter9 {
7985
8078
  id;
7986
8079
  samples;
7987
8080
  _ended = false;
@@ -8044,6 +8137,7 @@ var RemoteTrackResolver = class {
8044
8137
  observer.off("outbound-track-updated", onOutboundUpdated);
8045
8138
  this._pendingInboundTracks.clear();
8046
8139
  this._pendingOutboundTracks.clear();
8140
+ this._outboundTrackPublisherIds.clear();
8047
8141
  });
8048
8142
  observer.on("inbound-track-added", onInboundAdded);
8049
8143
  observer.on("inbound-track-removed", onInboundRemoved);
@@ -8055,6 +8149,8 @@ var RemoteTrackResolver = class {
8055
8149
  observedCall;
8056
8150
  resolvers;
8057
8151
  _publisherIdToOutboundTrack = /* @__PURE__ */ new Map();
8152
+ // The publisher id each outbound track was indexed under, so removal does not depend on its current attachments.
8153
+ _outboundTrackPublisherIds = /* @__PURE__ */ new Map();
8058
8154
  _subscriberIdToInboundTrack = /* @__PURE__ */ new Map();
8059
8155
  /**
8060
8156
  * Tracks whose publisher id the strategy could not resolve **yet**.
@@ -8130,6 +8226,7 @@ var RemoteTrackResolver = class {
8130
8226
  }
8131
8227
  this._pendingOutboundTracks.delete(outboundTrack);
8132
8228
  this._publisherIdToOutboundTrack.set(publisherId, outboundTrack);
8229
+ this._outboundTrackPublisherIds.set(outboundTrack, publisherId);
8133
8230
  this.observedCall.unconsumedOutboundTracks.add(outboundTrack);
8134
8231
  for (const observedClient of this.observedCall.observedClients.values()) {
8135
8232
  for (const observedPeerConnection of observedClient.observedPeerConnections.values()) {
@@ -8144,7 +8241,8 @@ var RemoteTrackResolver = class {
8144
8241
  }
8145
8242
  _removeOutboundTrack(outboundTrack) {
8146
8243
  this._pendingOutboundTracks.delete(outboundTrack);
8147
- const publisherId = this.resolvers.resolveOutboundTrackPublisherId(outboundTrack);
8244
+ const publisherId = this._outboundTrackPublisherIds.get(outboundTrack);
8245
+ this._outboundTrackPublisherIds.delete(outboundTrack);
8148
8246
  if (publisherId && this._publisherIdToOutboundTrack.get(publisherId) === outboundTrack) {
8149
8247
  this._publisherIdToOutboundTrack.delete(publisherId);
8150
8248
  }
@@ -8236,6 +8334,7 @@ export {
8236
8334
  RTT_HISTOGRAM_NUMBER_OF_BUCKETS,
8237
8335
  RemoteTrackResolver,
8238
8336
  RemoteTrackResolverValidator,
8337
+ RollingJsonlFileSink,
8239
8338
  SCORE_HISTOGRAM_BUCKET_WIDTH,
8240
8339
  SCORE_HISTOGRAM_NUMBER_OF_BUCKETS,
8241
8340
  SfuCongestionDetector,