moqtail 0.10.0 → 0.10.1

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/client.cjs CHANGED
@@ -6456,6 +6456,8 @@ var MOQtailClient = class _MOQtailClient {
6456
6456
  #isDestroyed = false;
6457
6457
  /** Internal monotonically increasing client-assigned request id counter (even/odd parity scheme advances by 2). */
6458
6458
  #dontUseRequestId = 0n;
6459
+ /** Active early discard policy; undefined means no per-stream deadline is applied. */
6460
+ #earlyDiscardPolicy;
6459
6461
  /**
6460
6462
  * TODO: onNamespaceAnnounced may be a better name
6461
6463
  * Fired when an PUBLISH_NAMESPACE control message is processed for a track namespace.
@@ -6907,6 +6909,25 @@ var MOQtailClient = class _MOQtailClient {
6907
6909
  return FullTrackName.tryNew("unknown", `track-${trackAlias}`);
6908
6910
  }
6909
6911
  }
6912
+ /**
6913
+ * Sets (or replaces) the early discard policy for incoming subgroup streams.
6914
+ *
6915
+ * When set, each incoming subgroup QUIC stream is given a deadline of `subgroupReceiveTimeout` ms to
6916
+ * complete. If the stream has not finished within that window it is cancelled — objects already
6917
+ * delivered to the subscription are kept, but no further objects arrive from that stream.
6918
+ *
6919
+ * The policy takes effect on the next stream accepted after this call. Passing a new config
6920
+ * replaces the previous one. Pass `undefined` to remove the policy.
6921
+ *
6922
+ * @example
6923
+ * ```ts
6924
+ * client.setEarlyDiscardPolicy({ subgroupReceiveTimeout: 2000 })
6925
+ * ```
6926
+ */
6927
+ setEarlyDiscardPolicy(config) {
6928
+ this.#ensureActive();
6929
+ this.#earlyDiscardPolicy = config;
6930
+ }
6910
6931
  /**
6911
6932
  * Gracefully terminates this {@link MOQtailClient} session and releases underlying {@link https://developer.mozilla.org/docs/Web/API/WebTransport | WebTransport} resources.
6912
6933
  *
@@ -7988,43 +8009,54 @@ var MOQtailClient = class _MOQtailClient {
7988
8009
  if (subscription) {
7989
8010
  subscription.streamsAccepted++;
7990
8011
  let firstObjectId = null;
7991
- while (true) {
7992
- const { done, value: nextObject } = await reader.read();
7993
- if (done) {
7994
- break;
7995
- }
7996
- if (nextObject) {
7997
- if (nextObject instanceof SubgroupObject) {
7998
- if (!firstObjectId) {
7999
- firstObjectId = nextObject.objectId;
8000
- }
8001
- let subgroupId = null;
8002
- if (SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8003
- subgroupId = 0n;
8004
- } else if (SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8005
- subgroupId = firstObjectId ?? null;
8006
- } else if (SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8007
- subgroupId = header.subgroupId ?? null;
8008
- }
8009
- const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8010
- if (!fullTrackName) {
8011
- throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8012
+ let subgroupTimeoutId;
8013
+ if (this.#earlyDiscardPolicy?.subgroupReceiveTimeout !== void 0) {
8014
+ subgroupTimeoutId = setTimeout(() => {
8015
+ reader.cancel("early discard: subgroupReceiveTimeout exceeded").catch(() => {
8016
+ });
8017
+ }, this.#earlyDiscardPolicy.subgroupReceiveTimeout);
8018
+ }
8019
+ try {
8020
+ while (true) {
8021
+ const { done, value: nextObject } = await reader.read();
8022
+ if (done) {
8023
+ break;
8024
+ }
8025
+ if (nextObject) {
8026
+ if (nextObject instanceof SubgroupObject) {
8027
+ if (!firstObjectId) {
8028
+ firstObjectId = nextObject.objectId;
8029
+ }
8030
+ let subgroupId = null;
8031
+ if (SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8032
+ subgroupId = 0n;
8033
+ } else if (SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8034
+ subgroupId = firstObjectId ?? null;
8035
+ } else if (SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8036
+ subgroupId = header.subgroupId ?? null;
8037
+ }
8038
+ const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8039
+ if (!fullTrackName) {
8040
+ throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8041
+ }
8042
+ const moqtObject = MoqtObject.fromSubgroupObject(
8043
+ nextObject,
8044
+ header.groupId,
8045
+ header.publisherPriority,
8046
+ subgroupId,
8047
+ fullTrackName
8048
+ );
8049
+ if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8050
+ if (subscription.largestLocation.compare(moqtObject.location) == -1)
8051
+ subscription.largestLocation = moqtObject.location;
8052
+ subscription.controller?.enqueue(moqtObject);
8053
+ continue;
8012
8054
  }
8013
- const moqtObject = MoqtObject.fromSubgroupObject(
8014
- nextObject,
8015
- header.groupId,
8016
- header.publisherPriority,
8017
- subgroupId,
8018
- fullTrackName
8019
- );
8020
- if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8021
- if (subscription.largestLocation.compare(moqtObject.location) == -1)
8022
- subscription.largestLocation = moqtObject.location;
8023
- subscription.controller?.enqueue(moqtObject);
8024
- continue;
8055
+ throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8025
8056
  }
8026
- throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8027
8057
  }
8058
+ } finally {
8059
+ if (subgroupTimeoutId !== void 0) clearTimeout(subgroupTimeoutId);
8028
8060
  }
8029
8061
  if (subscription.expectedStreams && subscription.expectedStreams === subscription.streamsAccepted) {
8030
8062
  subscription.controller?.close();
package/dist/client.d.cts CHANGED
@@ -799,6 +799,13 @@ type SwitchOptions = {
799
799
  * })
800
800
  * ```
801
801
  */
802
+ /**
803
+ * Configuration for the early discard policy applied to incoming subgroup streams.
804
+ */
805
+ type EarlyDiscardPolicyConfig = {
806
+ /** Cancel a subgroup QUIC stream if it has not fully completed within this many milliseconds. */
807
+ subgroupReceiveTimeout: number;
808
+ };
802
809
  type FetchOptions = {
803
810
  /** Request priority (0 = highest, 255 = lowest). Rounded & clamped. */
804
811
  priority: number;
@@ -1277,6 +1284,22 @@ declare class MOQtailClient {
1277
1284
  * ```
1278
1285
  */
1279
1286
  sendDatagram(trackAlias: bigint, object: MoqtObject): Promise<void>;
1287
+ /**
1288
+ * Sets (or replaces) the early discard policy for incoming subgroup streams.
1289
+ *
1290
+ * When set, each incoming subgroup QUIC stream is given a deadline of `subgroupReceiveTimeout` ms to
1291
+ * complete. If the stream has not finished within that window it is cancelled — objects already
1292
+ * delivered to the subscription are kept, but no further objects arrive from that stream.
1293
+ *
1294
+ * The policy takes effect on the next stream accepted after this call. Passing a new config
1295
+ * replaces the previous one. Pass `undefined` to remove the policy.
1296
+ *
1297
+ * @example
1298
+ * ```ts
1299
+ * client.setEarlyDiscardPolicy({ subgroupReceiveTimeout: 2000 })
1300
+ * ```
1301
+ */
1302
+ setEarlyDiscardPolicy(config: EarlyDiscardPolicyConfig | undefined): void;
1280
1303
  /**
1281
1304
  * Gracefully terminates this {@link MOQtailClient} session and releases underlying {@link https://developer.mozilla.org/docs/Web/API/WebTransport | WebTransport} resources.
1282
1305
  *
@@ -1820,4 +1843,4 @@ declare class RecvStream {
1820
1843
  static new(readStream: ReadableStream<Uint8Array>, partialDataTimeout?: number, onDataReceived?: (data: SubgroupObject | SubgroupHeader | FetchObject | FetchHeader) => void): Promise<RecvStream>;
1821
1844
  }
1822
1845
 
1823
- export { ControlStream, type FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, type LiveObjectSource, LiveTrackSource, MOQtailClient, type MOQtailClientOptions, type MOQtailRequest, MemoryObjectCache, type ObjectCache, type PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, type SubscribeOptions, SubscribePublication, SubscribeRequest, type SubscribeUpdateOptions, type SwitchOptions, type Track, type TrackSource, TrackStatusRequest };
1846
+ export { ControlStream, type EarlyDiscardPolicyConfig, type FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, type LiveObjectSource, LiveTrackSource, MOQtailClient, type MOQtailClientOptions, type MOQtailRequest, MemoryObjectCache, type ObjectCache, type PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, type SubscribeOptions, SubscribePublication, SubscribeRequest, type SubscribeUpdateOptions, type SwitchOptions, type Track, type TrackSource, TrackStatusRequest };
package/dist/client.d.ts CHANGED
@@ -799,6 +799,13 @@ type SwitchOptions = {
799
799
  * })
800
800
  * ```
801
801
  */
802
+ /**
803
+ * Configuration for the early discard policy applied to incoming subgroup streams.
804
+ */
805
+ type EarlyDiscardPolicyConfig = {
806
+ /** Cancel a subgroup QUIC stream if it has not fully completed within this many milliseconds. */
807
+ subgroupReceiveTimeout: number;
808
+ };
802
809
  type FetchOptions = {
803
810
  /** Request priority (0 = highest, 255 = lowest). Rounded & clamped. */
804
811
  priority: number;
@@ -1277,6 +1284,22 @@ declare class MOQtailClient {
1277
1284
  * ```
1278
1285
  */
1279
1286
  sendDatagram(trackAlias: bigint, object: MoqtObject): Promise<void>;
1287
+ /**
1288
+ * Sets (or replaces) the early discard policy for incoming subgroup streams.
1289
+ *
1290
+ * When set, each incoming subgroup QUIC stream is given a deadline of `subgroupReceiveTimeout` ms to
1291
+ * complete. If the stream has not finished within that window it is cancelled — objects already
1292
+ * delivered to the subscription are kept, but no further objects arrive from that stream.
1293
+ *
1294
+ * The policy takes effect on the next stream accepted after this call. Passing a new config
1295
+ * replaces the previous one. Pass `undefined` to remove the policy.
1296
+ *
1297
+ * @example
1298
+ * ```ts
1299
+ * client.setEarlyDiscardPolicy({ subgroupReceiveTimeout: 2000 })
1300
+ * ```
1301
+ */
1302
+ setEarlyDiscardPolicy(config: EarlyDiscardPolicyConfig | undefined): void;
1280
1303
  /**
1281
1304
  * Gracefully terminates this {@link MOQtailClient} session and releases underlying {@link https://developer.mozilla.org/docs/Web/API/WebTransport | WebTransport} resources.
1282
1305
  *
@@ -1820,4 +1843,4 @@ declare class RecvStream {
1820
1843
  static new(readStream: ReadableStream<Uint8Array>, partialDataTimeout?: number, onDataReceived?: (data: SubgroupObject | SubgroupHeader | FetchObject | FetchHeader) => void): Promise<RecvStream>;
1821
1844
  }
1822
1845
 
1823
- export { ControlStream, type FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, type LiveObjectSource, LiveTrackSource, MOQtailClient, type MOQtailClientOptions, type MOQtailRequest, MemoryObjectCache, type ObjectCache, type PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, type SubscribeOptions, SubscribePublication, SubscribeRequest, type SubscribeUpdateOptions, type SwitchOptions, type Track, type TrackSource, TrackStatusRequest };
1846
+ export { ControlStream, type EarlyDiscardPolicyConfig, type FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, type LiveObjectSource, LiveTrackSource, MOQtailClient, type MOQtailClientOptions, type MOQtailRequest, MemoryObjectCache, type ObjectCache, type PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, type SubscribeOptions, SubscribePublication, SubscribeRequest, type SubscribeUpdateOptions, type SwitchOptions, type Track, type TrackSource, TrackStatusRequest };
package/dist/client.js CHANGED
@@ -6454,6 +6454,8 @@ var MOQtailClient = class _MOQtailClient {
6454
6454
  #isDestroyed = false;
6455
6455
  /** Internal monotonically increasing client-assigned request id counter (even/odd parity scheme advances by 2). */
6456
6456
  #dontUseRequestId = 0n;
6457
+ /** Active early discard policy; undefined means no per-stream deadline is applied. */
6458
+ #earlyDiscardPolicy;
6457
6459
  /**
6458
6460
  * TODO: onNamespaceAnnounced may be a better name
6459
6461
  * Fired when an PUBLISH_NAMESPACE control message is processed for a track namespace.
@@ -6905,6 +6907,25 @@ var MOQtailClient = class _MOQtailClient {
6905
6907
  return FullTrackName.tryNew("unknown", `track-${trackAlias}`);
6906
6908
  }
6907
6909
  }
6910
+ /**
6911
+ * Sets (or replaces) the early discard policy for incoming subgroup streams.
6912
+ *
6913
+ * When set, each incoming subgroup QUIC stream is given a deadline of `subgroupReceiveTimeout` ms to
6914
+ * complete. If the stream has not finished within that window it is cancelled — objects already
6915
+ * delivered to the subscription are kept, but no further objects arrive from that stream.
6916
+ *
6917
+ * The policy takes effect on the next stream accepted after this call. Passing a new config
6918
+ * replaces the previous one. Pass `undefined` to remove the policy.
6919
+ *
6920
+ * @example
6921
+ * ```ts
6922
+ * client.setEarlyDiscardPolicy({ subgroupReceiveTimeout: 2000 })
6923
+ * ```
6924
+ */
6925
+ setEarlyDiscardPolicy(config) {
6926
+ this.#ensureActive();
6927
+ this.#earlyDiscardPolicy = config;
6928
+ }
6908
6929
  /**
6909
6930
  * Gracefully terminates this {@link MOQtailClient} session and releases underlying {@link https://developer.mozilla.org/docs/Web/API/WebTransport | WebTransport} resources.
6910
6931
  *
@@ -7986,43 +8007,54 @@ var MOQtailClient = class _MOQtailClient {
7986
8007
  if (subscription) {
7987
8008
  subscription.streamsAccepted++;
7988
8009
  let firstObjectId = null;
7989
- while (true) {
7990
- const { done, value: nextObject } = await reader.read();
7991
- if (done) {
7992
- break;
7993
- }
7994
- if (nextObject) {
7995
- if (nextObject instanceof SubgroupObject) {
7996
- if (!firstObjectId) {
7997
- firstObjectId = nextObject.objectId;
7998
- }
7999
- let subgroupId = null;
8000
- if (SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8001
- subgroupId = 0n;
8002
- } else if (SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8003
- subgroupId = firstObjectId ?? null;
8004
- } else if (SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8005
- subgroupId = header.subgroupId ?? null;
8006
- }
8007
- const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8008
- if (!fullTrackName) {
8009
- throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8010
+ let subgroupTimeoutId;
8011
+ if (this.#earlyDiscardPolicy?.subgroupReceiveTimeout !== void 0) {
8012
+ subgroupTimeoutId = setTimeout(() => {
8013
+ reader.cancel("early discard: subgroupReceiveTimeout exceeded").catch(() => {
8014
+ });
8015
+ }, this.#earlyDiscardPolicy.subgroupReceiveTimeout);
8016
+ }
8017
+ try {
8018
+ while (true) {
8019
+ const { done, value: nextObject } = await reader.read();
8020
+ if (done) {
8021
+ break;
8022
+ }
8023
+ if (nextObject) {
8024
+ if (nextObject instanceof SubgroupObject) {
8025
+ if (!firstObjectId) {
8026
+ firstObjectId = nextObject.objectId;
8027
+ }
8028
+ let subgroupId = null;
8029
+ if (SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8030
+ subgroupId = 0n;
8031
+ } else if (SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8032
+ subgroupId = firstObjectId ?? null;
8033
+ } else if (SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8034
+ subgroupId = header.subgroupId ?? null;
8035
+ }
8036
+ const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8037
+ if (!fullTrackName) {
8038
+ throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8039
+ }
8040
+ const moqtObject = MoqtObject.fromSubgroupObject(
8041
+ nextObject,
8042
+ header.groupId,
8043
+ header.publisherPriority,
8044
+ subgroupId,
8045
+ fullTrackName
8046
+ );
8047
+ if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8048
+ if (subscription.largestLocation.compare(moqtObject.location) == -1)
8049
+ subscription.largestLocation = moqtObject.location;
8050
+ subscription.controller?.enqueue(moqtObject);
8051
+ continue;
8010
8052
  }
8011
- const moqtObject = MoqtObject.fromSubgroupObject(
8012
- nextObject,
8013
- header.groupId,
8014
- header.publisherPriority,
8015
- subgroupId,
8016
- fullTrackName
8017
- );
8018
- if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8019
- if (subscription.largestLocation.compare(moqtObject.location) == -1)
8020
- subscription.largestLocation = moqtObject.location;
8021
- subscription.controller?.enqueue(moqtObject);
8022
- continue;
8053
+ throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8023
8054
  }
8024
- throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8025
8055
  }
8056
+ } finally {
8057
+ if (subgroupTimeoutId !== void 0) clearTimeout(subgroupTimeoutId);
8026
8058
  }
8027
8059
  if (subscription.expectedStreams && subscription.expectedStreams === subscription.streamsAccepted) {
8028
8060
  subscription.controller?.close();
package/dist/index.cjs CHANGED
@@ -6950,6 +6950,8 @@ var MOQtailClient = class _MOQtailClient {
6950
6950
  #isDestroyed = false;
6951
6951
  /** Internal monotonically increasing client-assigned request id counter (even/odd parity scheme advances by 2). */
6952
6952
  #dontUseRequestId = 0n;
6953
+ /** Active early discard policy; undefined means no per-stream deadline is applied. */
6954
+ #earlyDiscardPolicy;
6953
6955
  /**
6954
6956
  * TODO: onNamespaceAnnounced may be a better name
6955
6957
  * Fired when an PUBLISH_NAMESPACE control message is processed for a track namespace.
@@ -7401,6 +7403,25 @@ var MOQtailClient = class _MOQtailClient {
7401
7403
  return FullTrackName.tryNew("unknown", `track-${trackAlias}`);
7402
7404
  }
7403
7405
  }
7406
+ /**
7407
+ * Sets (or replaces) the early discard policy for incoming subgroup streams.
7408
+ *
7409
+ * When set, each incoming subgroup QUIC stream is given a deadline of `subgroupReceiveTimeout` ms to
7410
+ * complete. If the stream has not finished within that window it is cancelled — objects already
7411
+ * delivered to the subscription are kept, but no further objects arrive from that stream.
7412
+ *
7413
+ * The policy takes effect on the next stream accepted after this call. Passing a new config
7414
+ * replaces the previous one. Pass `undefined` to remove the policy.
7415
+ *
7416
+ * @example
7417
+ * ```ts
7418
+ * client.setEarlyDiscardPolicy({ subgroupReceiveTimeout: 2000 })
7419
+ * ```
7420
+ */
7421
+ setEarlyDiscardPolicy(config) {
7422
+ this.#ensureActive();
7423
+ this.#earlyDiscardPolicy = config;
7424
+ }
7404
7425
  /**
7405
7426
  * Gracefully terminates this {@link MOQtailClient} session and releases underlying {@link https://developer.mozilla.org/docs/Web/API/WebTransport | WebTransport} resources.
7406
7427
  *
@@ -8482,43 +8503,54 @@ var MOQtailClient = class _MOQtailClient {
8482
8503
  if (subscription) {
8483
8504
  subscription.streamsAccepted++;
8484
8505
  let firstObjectId = null;
8485
- while (true) {
8486
- const { done, value: nextObject } = await reader.read();
8487
- if (done) {
8488
- break;
8489
- }
8490
- if (nextObject) {
8491
- if (nextObject instanceof SubgroupObject) {
8492
- if (!firstObjectId) {
8493
- firstObjectId = nextObject.objectId;
8494
- }
8495
- let subgroupId = null;
8496
- if (exports.SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8497
- subgroupId = 0n;
8498
- } else if (exports.SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8499
- subgroupId = firstObjectId ?? null;
8500
- } else if (exports.SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8501
- subgroupId = header.subgroupId ?? null;
8502
- }
8503
- const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8504
- if (!fullTrackName) {
8505
- throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8506
+ let subgroupTimeoutId;
8507
+ if (this.#earlyDiscardPolicy?.subgroupReceiveTimeout !== void 0) {
8508
+ subgroupTimeoutId = setTimeout(() => {
8509
+ reader.cancel("early discard: subgroupReceiveTimeout exceeded").catch(() => {
8510
+ });
8511
+ }, this.#earlyDiscardPolicy.subgroupReceiveTimeout);
8512
+ }
8513
+ try {
8514
+ while (true) {
8515
+ const { done, value: nextObject } = await reader.read();
8516
+ if (done) {
8517
+ break;
8518
+ }
8519
+ if (nextObject) {
8520
+ if (nextObject instanceof SubgroupObject) {
8521
+ if (!firstObjectId) {
8522
+ firstObjectId = nextObject.objectId;
8523
+ }
8524
+ let subgroupId = null;
8525
+ if (exports.SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8526
+ subgroupId = 0n;
8527
+ } else if (exports.SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8528
+ subgroupId = firstObjectId ?? null;
8529
+ } else if (exports.SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8530
+ subgroupId = header.subgroupId ?? null;
8531
+ }
8532
+ const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8533
+ if (!fullTrackName) {
8534
+ throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8535
+ }
8536
+ const moqtObject = MoqtObject.fromSubgroupObject(
8537
+ nextObject,
8538
+ header.groupId,
8539
+ header.publisherPriority,
8540
+ subgroupId,
8541
+ fullTrackName
8542
+ );
8543
+ if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8544
+ if (subscription.largestLocation.compare(moqtObject.location) == -1)
8545
+ subscription.largestLocation = moqtObject.location;
8546
+ subscription.controller?.enqueue(moqtObject);
8547
+ continue;
8506
8548
  }
8507
- const moqtObject = MoqtObject.fromSubgroupObject(
8508
- nextObject,
8509
- header.groupId,
8510
- header.publisherPriority,
8511
- subgroupId,
8512
- fullTrackName
8513
- );
8514
- if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8515
- if (subscription.largestLocation.compare(moqtObject.location) == -1)
8516
- subscription.largestLocation = moqtObject.location;
8517
- subscription.controller?.enqueue(moqtObject);
8518
- continue;
8549
+ throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8519
8550
  }
8520
- throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8521
8551
  }
8552
+ } finally {
8553
+ if (subgroupTimeoutId !== void 0) clearTimeout(subgroupTimeoutId);
8522
8554
  }
8523
8555
  if (subscription.expectedStreams && subscription.expectedStreams === subscription.streamsAccepted) {
8524
8556
  subscription.controller?.close();
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { ControlStream, FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, LiveObjectSource, LiveTrackSource, MOQtailClient, MOQtailClientOptions, MOQtailRequest, MemoryObjectCache, ObjectCache, PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, SubscribeOptions, SubscribePublication, SubscribeRequest, SubscribeUpdateOptions, SwitchOptions, Track, TrackSource, TrackStatusRequest } from './client.cjs';
1
+ export { ControlStream, EarlyDiscardPolicyConfig, FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, LiveObjectSource, LiveTrackSource, MOQtailClient, MOQtailClientOptions, MOQtailRequest, MemoryObjectCache, ObjectCache, PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, SubscribeOptions, SubscribePublication, SubscribeRequest, SubscribeUpdateOptions, SwitchOptions, Track, TrackSource, TrackStatusRequest } from './client.cjs';
2
2
  export { AudioLevel, CMSF, CMSFCatalog, CMSFTrack, CaptureTimestamp, CastingError, ExtensionHeader, ExtensionHeaders, ImmutableExtensionsObjectExtension, InternalError, InvalidTypeError, InvalidUTF8Error, KeyValueFormattingError, LengthExceedsMaxError, MOQtailError, NotEnoughBytesError, ObjectExtension, PriorGroupIdGapExtension, PriorObjectIdGapExtension, ProtocolViolationError, RequestIdError, Switch, TerminationCode, TerminationError, TimeoutError, TrackNameError, UnknownObjectExtension, VarIntOverflowError, VideoConfig, VideoFrameMarking } from './model.cjs';
3
3
  export { A as AuthTokenVariant, a as AuthorizationToken, B as BaseByteBuffer, b as ByteBuffer, C as ClientSetup, c as ControlMessage, d as ControlMessageType, D as Datagram, e as DefaultPublisherGroupOrderExtension, f as DefaultPublisherPriorityExtension, g as DeliveryTimeout, h as DeliveryTimeoutExtension, i as DynamicGroupsExtension, E as EndOfRangeKind, j as Expires, F as Fetch, k as FetchCancel, l as FetchHeader, m as FetchHeaderType, n as FetchObject, o as FetchObjectContext, p as FetchOk, q as FetchType, r as FilterType, s as Forward, t as FrozenByteBuffer, u as FullTrackName, G as GoAway, v as GroupOrder, w as GroupOrderParam, H as Header, I as ImmutableExtensionsExtension, K as KeyValuePair, L as LOCHeaderExtensionId, x as LargestObject, y as Location, M as MAX_FULL_TRACK_NAME_LENGTH, z as MAX_NAMESPACE_TUPLE_COUNT, J as MAX_REASON_PHRASE_LEN, N as MaxAuthTokenCacheSize, O as MaxCacheDurationExtension, P as MaxRequestId, Q as MaxRequestIdParameter, R as MessageParameter, S as MessageParameterType, T as MessageParameters, U as MoqtObject, V as Namespace, W as NamespaceDone, X as NamespaceSubscribeOptions, Y as NewGroupRequest, Z as ObjectDatagramType, _ as ObjectForwardingPreference, $ as ObjectStatus, a0 as Parameter, a1 as Path, a2 as Publish, a3 as PublishDone, a4 as PublishDoneStatusCode, a5 as PublishNamespace, a6 as PublishNamespaceCancel, a7 as PublishNamespaceDone, a8 as PublishOk, a9 as ReasonPhrase, aa as RequestError, ab as RequestErrorCode, ac as RequestIdMap, ad as RequestOk, ae as RequestUpdate, af as RequestsBlocked, ag as SUPPORTED_VERSIONS, ah as ServerSetup, ai as SetupParameter, aj as SetupParameterType, ak as SetupParameters, al as SubgroupHeader, am as SubgroupHeaderType, an as SubgroupObject, ao as Subscribe, ap as SubscribeNamespace, aq as SubscribeOk, ar as SubscriberPriority, as as SubscriptionFilter, at as TokenAliasType, au as TrackExtension, av as TrackExtensionType, aw as TrackStatus, ax as TrackStatusCode, ay as Tuple, az as TupleField, aA as UnknownTrackExtension, aB as Unsubscribe, aC as UnsubscribeNamespace, aD as applyMessageParameterUpdate, aE as controlMessageTypeFromBigInt, aF as fetchTypeFromBigInt, aG as filterTypeFromBigInt, aH as groupOrderFromNumber, aI as isBytes, aJ as isVarInt, aK as locHeaderExtensionIdFromNumber, aL as messageParameterTypeFromNumber, aM as publishDoneStatusCodeFromBigInt, aN as requestErrorCodeFromBigInt, aO as setupParameterTypeFromNumber, aP as tokenAliasTypeFromNumber, aQ as trackStatusCodeFromBigInt } from './setup_parameter-BOeGq6Mv.cjs';
4
4
  export { ClockNormalizer, NetworkTelemetry } from './util.cjs';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ControlStream, FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, LiveObjectSource, LiveTrackSource, MOQtailClient, MOQtailClientOptions, MOQtailRequest, MemoryObjectCache, ObjectCache, PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, SubscribeOptions, SubscribePublication, SubscribeRequest, SubscribeUpdateOptions, SwitchOptions, Track, TrackSource, TrackStatusRequest } from './client.js';
1
+ export { ControlStream, EarlyDiscardPolicyConfig, FetchOptions, FetchPublication, FetchRequest, HybridTrackSource, LiveObjectSource, LiveTrackSource, MOQtailClient, MOQtailClientOptions, MOQtailRequest, MemoryObjectCache, ObjectCache, PastObjectSource, PublishNamespaceRequest, PublishPublication, PublishRequest, RecvDatagramStream, RecvStream, RingBufferObjectCache, SendDatagramStream, SendStream, StaticTrackSource, SubscribeNamespaceRequest, SubscribeOptions, SubscribePublication, SubscribeRequest, SubscribeUpdateOptions, SwitchOptions, Track, TrackSource, TrackStatusRequest } from './client.js';
2
2
  export { AudioLevel, CMSF, CMSFCatalog, CMSFTrack, CaptureTimestamp, CastingError, ExtensionHeader, ExtensionHeaders, ImmutableExtensionsObjectExtension, InternalError, InvalidTypeError, InvalidUTF8Error, KeyValueFormattingError, LengthExceedsMaxError, MOQtailError, NotEnoughBytesError, ObjectExtension, PriorGroupIdGapExtension, PriorObjectIdGapExtension, ProtocolViolationError, RequestIdError, Switch, TerminationCode, TerminationError, TimeoutError, TrackNameError, UnknownObjectExtension, VarIntOverflowError, VideoConfig, VideoFrameMarking } from './model.js';
3
3
  export { A as AuthTokenVariant, a as AuthorizationToken, B as BaseByteBuffer, b as ByteBuffer, C as ClientSetup, c as ControlMessage, d as ControlMessageType, D as Datagram, e as DefaultPublisherGroupOrderExtension, f as DefaultPublisherPriorityExtension, g as DeliveryTimeout, h as DeliveryTimeoutExtension, i as DynamicGroupsExtension, E as EndOfRangeKind, j as Expires, F as Fetch, k as FetchCancel, l as FetchHeader, m as FetchHeaderType, n as FetchObject, o as FetchObjectContext, p as FetchOk, q as FetchType, r as FilterType, s as Forward, t as FrozenByteBuffer, u as FullTrackName, G as GoAway, v as GroupOrder, w as GroupOrderParam, H as Header, I as ImmutableExtensionsExtension, K as KeyValuePair, L as LOCHeaderExtensionId, x as LargestObject, y as Location, M as MAX_FULL_TRACK_NAME_LENGTH, z as MAX_NAMESPACE_TUPLE_COUNT, J as MAX_REASON_PHRASE_LEN, N as MaxAuthTokenCacheSize, O as MaxCacheDurationExtension, P as MaxRequestId, Q as MaxRequestIdParameter, R as MessageParameter, S as MessageParameterType, T as MessageParameters, U as MoqtObject, V as Namespace, W as NamespaceDone, X as NamespaceSubscribeOptions, Y as NewGroupRequest, Z as ObjectDatagramType, _ as ObjectForwardingPreference, $ as ObjectStatus, a0 as Parameter, a1 as Path, a2 as Publish, a3 as PublishDone, a4 as PublishDoneStatusCode, a5 as PublishNamespace, a6 as PublishNamespaceCancel, a7 as PublishNamespaceDone, a8 as PublishOk, a9 as ReasonPhrase, aa as RequestError, ab as RequestErrorCode, ac as RequestIdMap, ad as RequestOk, ae as RequestUpdate, af as RequestsBlocked, ag as SUPPORTED_VERSIONS, ah as ServerSetup, ai as SetupParameter, aj as SetupParameterType, ak as SetupParameters, al as SubgroupHeader, am as SubgroupHeaderType, an as SubgroupObject, ao as Subscribe, ap as SubscribeNamespace, aq as SubscribeOk, ar as SubscriberPriority, as as SubscriptionFilter, at as TokenAliasType, au as TrackExtension, av as TrackExtensionType, aw as TrackStatus, ax as TrackStatusCode, ay as Tuple, az as TupleField, aA as UnknownTrackExtension, aB as Unsubscribe, aC as UnsubscribeNamespace, aD as applyMessageParameterUpdate, aE as controlMessageTypeFromBigInt, aF as fetchTypeFromBigInt, aG as filterTypeFromBigInt, aH as groupOrderFromNumber, aI as isBytes, aJ as isVarInt, aK as locHeaderExtensionIdFromNumber, aL as messageParameterTypeFromNumber, aM as publishDoneStatusCodeFromBigInt, aN as requestErrorCodeFromBigInt, aO as setupParameterTypeFromNumber, aP as tokenAliasTypeFromNumber, aQ as trackStatusCodeFromBigInt } from './setup_parameter-BOeGq6Mv.js';
4
4
  export { ClockNormalizer, NetworkTelemetry } from './util.js';
package/dist/index.js CHANGED
@@ -6948,6 +6948,8 @@ var MOQtailClient = class _MOQtailClient {
6948
6948
  #isDestroyed = false;
6949
6949
  /** Internal monotonically increasing client-assigned request id counter (even/odd parity scheme advances by 2). */
6950
6950
  #dontUseRequestId = 0n;
6951
+ /** Active early discard policy; undefined means no per-stream deadline is applied. */
6952
+ #earlyDiscardPolicy;
6951
6953
  /**
6952
6954
  * TODO: onNamespaceAnnounced may be a better name
6953
6955
  * Fired when an PUBLISH_NAMESPACE control message is processed for a track namespace.
@@ -7399,6 +7401,25 @@ var MOQtailClient = class _MOQtailClient {
7399
7401
  return FullTrackName.tryNew("unknown", `track-${trackAlias}`);
7400
7402
  }
7401
7403
  }
7404
+ /**
7405
+ * Sets (or replaces) the early discard policy for incoming subgroup streams.
7406
+ *
7407
+ * When set, each incoming subgroup QUIC stream is given a deadline of `subgroupReceiveTimeout` ms to
7408
+ * complete. If the stream has not finished within that window it is cancelled — objects already
7409
+ * delivered to the subscription are kept, but no further objects arrive from that stream.
7410
+ *
7411
+ * The policy takes effect on the next stream accepted after this call. Passing a new config
7412
+ * replaces the previous one. Pass `undefined` to remove the policy.
7413
+ *
7414
+ * @example
7415
+ * ```ts
7416
+ * client.setEarlyDiscardPolicy({ subgroupReceiveTimeout: 2000 })
7417
+ * ```
7418
+ */
7419
+ setEarlyDiscardPolicy(config) {
7420
+ this.#ensureActive();
7421
+ this.#earlyDiscardPolicy = config;
7422
+ }
7402
7423
  /**
7403
7424
  * Gracefully terminates this {@link MOQtailClient} session and releases underlying {@link https://developer.mozilla.org/docs/Web/API/WebTransport | WebTransport} resources.
7404
7425
  *
@@ -8480,43 +8501,54 @@ var MOQtailClient = class _MOQtailClient {
8480
8501
  if (subscription) {
8481
8502
  subscription.streamsAccepted++;
8482
8503
  let firstObjectId = null;
8483
- while (true) {
8484
- const { done, value: nextObject } = await reader.read();
8485
- if (done) {
8486
- break;
8487
- }
8488
- if (nextObject) {
8489
- if (nextObject instanceof SubgroupObject) {
8490
- if (!firstObjectId) {
8491
- firstObjectId = nextObject.objectId;
8492
- }
8493
- let subgroupId = null;
8494
- if (SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8495
- subgroupId = 0n;
8496
- } else if (SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8497
- subgroupId = firstObjectId ?? null;
8498
- } else if (SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8499
- subgroupId = header.subgroupId ?? null;
8500
- }
8501
- const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8502
- if (!fullTrackName) {
8503
- throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8504
+ let subgroupTimeoutId;
8505
+ if (this.#earlyDiscardPolicy?.subgroupReceiveTimeout !== void 0) {
8506
+ subgroupTimeoutId = setTimeout(() => {
8507
+ reader.cancel("early discard: subgroupReceiveTimeout exceeded").catch(() => {
8508
+ });
8509
+ }, this.#earlyDiscardPolicy.subgroupReceiveTimeout);
8510
+ }
8511
+ try {
8512
+ while (true) {
8513
+ const { done, value: nextObject } = await reader.read();
8514
+ if (done) {
8515
+ break;
8516
+ }
8517
+ if (nextObject) {
8518
+ if (nextObject instanceof SubgroupObject) {
8519
+ if (!firstObjectId) {
8520
+ firstObjectId = nextObject.objectId;
8521
+ }
8522
+ let subgroupId = null;
8523
+ if (SubgroupHeaderType.isSubgroupIdZero(header.type)) {
8524
+ subgroupId = 0n;
8525
+ } else if (SubgroupHeaderType.isSubgroupIdFirstObjectId(header.type)) {
8526
+ subgroupId = firstObjectId ?? null;
8527
+ } else if (SubgroupHeaderType.hasExplicitSubgroupId(header.type)) {
8528
+ subgroupId = header.subgroupId ?? null;
8529
+ }
8530
+ const fullTrackName = this.aliasFullTrackNameMap.get(header.trackAlias);
8531
+ if (!fullTrackName) {
8532
+ throw new ProtocolViolationError("MOQtailClient", "No full track name for received track alias");
8533
+ }
8534
+ const moqtObject = MoqtObject.fromSubgroupObject(
8535
+ nextObject,
8536
+ header.groupId,
8537
+ header.publisherPriority,
8538
+ subgroupId,
8539
+ fullTrackName
8540
+ );
8541
+ if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8542
+ if (subscription.largestLocation.compare(moqtObject.location) == -1)
8543
+ subscription.largestLocation = moqtObject.location;
8544
+ subscription.controller?.enqueue(moqtObject);
8545
+ continue;
8504
8546
  }
8505
- const moqtObject = MoqtObject.fromSubgroupObject(
8506
- nextObject,
8507
- header.groupId,
8508
- header.publisherPriority,
8509
- subgroupId,
8510
- fullTrackName
8511
- );
8512
- if (!subscription.largestLocation) subscription.largestLocation = moqtObject.location;
8513
- if (subscription.largestLocation.compare(moqtObject.location) == -1)
8514
- subscription.largestLocation = moqtObject.location;
8515
- subscription.controller?.enqueue(moqtObject);
8516
- continue;
8547
+ throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8517
8548
  }
8518
- throw new ProtocolViolationError("MOQtailClient", "Received fetch object after subgroup header");
8519
8549
  }
8550
+ } finally {
8551
+ if (subgroupTimeoutId !== void 0) clearTimeout(subgroupTimeoutId);
8520
8552
  }
8521
8553
  if (subscription.expectedStreams && subscription.expectedStreams === subscription.streamsAccepted) {
8522
8554
  subscription.controller?.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moqtail",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Media Over QUIC Transport client implementation",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",