@torrent-tv/proxy 2.107.40 → 2.107.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.107.40",
3
+ "version": "2.107.41",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -629,7 +629,7 @@ export function wireOutputs({
629
629
  viewerSecondsOn: (output, consumerId, now) => viewerSecondsOn(parts.viewers, output, consumerId, now),
630
630
  minimumBufferSecondsFor: (output) => minimumBufferSecondsOf(output),
631
631
  // What this proxy may keep of downloaded pieces on disk.
632
- sourceStorageBytes: () => parts.machineBudget?.allowedFor?.("spilled pieces") ?? null,
632
+ sourceStorageBytes: () => parts.machineBudget?.allowedFor("spilled pieces") ?? null,
633
633
  getSourceStats: (...args) => parts.getSourceStats?.(...args) ?? null,
634
634
  disposeSession: (...args) => parts.lifecycle.disposeSession(...args),
635
635
  planEncodersSoon: (...args) => parts.encodeRuns.planEncodersSoon(...args),
@@ -194,6 +194,8 @@ export function wireMachineBudget({
194
194
 
195
195
  return {
196
196
  capacityOf: name => budget.capacityOf(name),
197
+ /** What one claimant was last allowed, or null before a measured division. */
198
+ allowedFor: name => budget.allowedFor(name),
197
199
  /**
198
200
  * @param {string | null} [resource] - One resource to revise; all when absent.
199
201
  * @param {string | null} [because] - Who asked, for the revision's line.
@@ -740,12 +740,13 @@ export function predictPlaybackReadiness(input = {}) {
740
740
  // share (Home Assistant 2026-10-26, torrent-tv/meta#239). Said as such, and
741
741
  // the viewer keeps waiting rather than starting into stops.
742
742
  const storageBytes = Number(input.sourceStorageBytes);
743
- if (Number.isFinite(delay) && Number.isFinite(storageBytes) && storageBytes >= 0 && tailStock.length > 0) {
744
- const cushionBytes = tailStock.reduce((sum, part) => sum + Math.max(0, part.totalBytes - part.missingBytes) +
743
+ const tailParts = tailStock.filter(part => !part.skipped);
744
+ if (Number.isFinite(delay) && Number.isFinite(storageBytes) && storageBytes >= 0 && tailParts.length > 0) {
745
+ const cushionBytes = tailParts.reduce((sum, part) => sum + Math.max(0, part.totalBytes - part.missingBytes) +
745
746
  Math.max(0, part.missingBytes - part.bytesPerSecond * Math.max(0, part.finish - delay)), 0);
746
747
  if (cushionBytes > storageBytes) {
747
748
  return { ...result(false, null, buffered, reserve, null, "cushion-exceeds-storage", preparedSegments),
748
- cushionBytes, storageBytes };
749
+ cushionBytes, storageBytes, tail: tailStock };
749
750
  }
750
751
  }
751
752
  const originReady = trackState.every(({ clientRanges }) => compare(contiguousEnd(clientRanges, at), at) > 0);
@@ -756,6 +757,9 @@ export function predictPlaybackReadiness(input = {}) {
756
757
  return { ...forecast, measuredAt: now, predictedStartAt: Number.isFinite(delay) ? now + delay * 1000 : null,
757
758
  firstRequiredSegmentsSeconds,
758
759
  firstRequiredStages,
760
+ // What the rest of the film weighs per source, at what rate it comes and
761
+ // when, or why a source has none: said in the readiness line.
762
+ tail: tailStock,
759
763
  // The piece whose `kind` — its production, or its delivery to the page —
760
764
  // comes `atSeconds` from now while the media before it lasts
761
765
  // `aheadSeconds`: the difference is the delay.
@@ -801,7 +805,11 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
801
805
  const sourceDurations = new Map();
802
806
  for (const state of trackState) {
803
807
  const seconds = Math.max(0, state.tailEndSeconds - tailStart) * state.tailWorkFraction;
804
- if (!(seconds > 0)) continue;
808
+ if (!(seconds > 0)) {
809
+ stock?.push({ track: state.track.id, skipped: "no-work", tailStart, tailEnd: state.tailEndSeconds,
810
+ workFraction: state.tailWorkFraction, tailSegments: state.tailSegmentCount });
811
+ continue;
812
+ }
805
813
  for (const sourceId of new Set(state.track.sourceIds ?? [])) {
806
814
  sourceDurations.set(sourceId, Math.max(sourceDurations.get(sourceId) ?? 0, seconds));
807
815
  }
@@ -811,7 +819,10 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
811
819
  for (const [sourceId, seconds] of sourceDurations) {
812
820
  const source = sourceState.get(sourceId);
813
821
  if (!source) return { reason: "source-measurement-unavailable" };
814
- if (source.source.complete === true) continue;
822
+ if (source.source.complete === true) {
823
+ stock?.push({ sourceId, skipped: "complete" });
824
+ continue;
825
+ }
815
826
  // Held means on this proxy now, past the last byte the near window's
816
827
  // pieces read: those are counted above, and the window's planned arrivals
817
828
  // counted again as held took 117 s of film off a 90-minute tail. The
@@ -821,7 +832,10 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
821
832
  heldBeyond(source.source.residence, windowEnd), windowEnd);
822
833
  if (bytes === null) return { reason: "source-measurement-unavailable",
823
834
  unavailableSource: { sourceId, phase: "tail" } };
824
- if (!(bytes > 0)) continue;
835
+ if (!(bytes > 0)) {
836
+ stock?.push({ sourceId, skipped: "held", seconds, windowEnd });
837
+ continue;
838
+ }
825
839
  const { service } = source;
826
840
  const rate = service.tailRate ?? service.rate;
827
841
  if (!rate || !(rate.rateAt(0) > 0)) {
@@ -832,7 +846,7 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
832
846
  sourceArrivalAt = Math.max(sourceArrivalAt, service.finish);
833
847
  // What this stretch weighs and what it still lacks: the stock the start
834
848
  // asks to hold is read from these once the start's delay is known.
835
- stock?.push({ totalBytes: estimateUnmappedSourceBytes(source.source, seconds, duration, []) ?? bytes,
849
+ stock?.push({ sourceId, seconds, windowEnd, totalBytes:estimateUnmappedSourceBytes(source.source, seconds, duration, []) ?? bytes,
836
850
  missingBytes: bytes, bytesPerSecond: rate.rateAt(0), finish: service.finish });
837
851
  const state = trackState.find(candidate => candidate.track.sourceIds?.includes(sourceId));
838
852
  const index = state?.segments.at(-1)?.index ?? 0;