@torrent-tv/proxy 2.107.55 → 2.107.56

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.55",
3
+ "version": "2.107.56",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -108,7 +108,7 @@ function sourceCaptureOf(stats, actionId, generation) {
108
108
  return { ...source, actionId: actionId ?? null, generation: Number.isSafeInteger(generation) ? generation : null };
109
109
  }
110
110
 
111
- function encoderCaptureOf(sampleCaptures, liveRuns, actionId, generation) {
111
+ export function encoderCaptureOf(sampleCaptures, liveRuns, actionId, generation) {
112
112
  const runCaptures = (liveRuns ?? []).map(run => ({ owner: "proxy-encoder", clock: "performance",
113
113
  clockEpoch: run.progress?.clockEpoch, startedAt: run.progress?.updatedAtMonotonic,
114
114
  completedAt: run.progress?.updatedAtMonotonic, revision: run.progress?.updatedAtMonotonic,
@@ -117,19 +117,56 @@ function encoderCaptureOf(sampleCaptures, liveRuns, actionId, generation) {
117
117
  Number.isFinite(run.progress?.updatedAtMonotonic) ? null : "encoder-progress-capture-unavailable" }));
118
118
  const captures = [...(Array.isArray(sampleCaptures) ? sampleCaptures : []), ...runCaptures];
119
119
  const epoch = captures[0]?.clockEpoch;
120
- const valid = captures.length > 0 && epoch === performance.timeOrigin && captures.every(capture =>
121
- capture.owner === "proxy-encoder" && capture.clock === "performance" &&
120
+ const samplesOrdered = (Array.isArray(sampleCaptures) ? sampleCaptures : []).every((capture, index, samples) =>
121
+ index === 0 || capture.revision >= samples[index - 1].revision);
122
+ const valid = captures.length > 0 && samplesOrdered && epoch === performance.timeOrigin && captures.every(capture =>
123
+ capture.owner === "proxy-encoder" && capture.clock === "performance" && capture.validity === "valid" &&
122
124
  Number.isFinite(capture.clockEpoch) && capture.clockEpoch === epoch && Number.isFinite(capture.startedAt) &&
123
125
  Number.isFinite(capture.completedAt) && capture.completedAt >= capture.startedAt &&
124
126
  Number.isFinite(capture.revision));
125
- if (!valid) return unknownCapture("proxy-encoder", actionId, generation, "encoder-capture-unavailable");
127
+ if (!valid) {
128
+ const projected = captures.find(capture => capture.provenance === "projected");
129
+ return { ...unknownCapture("proxy-encoder", actionId, generation,
130
+ projected?.unknownReason ?? "encoder-capture-unavailable"),
131
+ samples: captures, ...(projected ? { provenance: projected.provenance, projectionSource: projected.source } : {}) };
132
+ }
126
133
  return { owner: "proxy-encoder", clock: "performance", clockEpoch: epoch,
127
134
  startedAt: Math.min(...captures.map(capture => capture.startedAt)),
128
135
  completedAt: Math.max(...captures.map(capture => capture.completedAt)), actionId: actionId ?? null,
129
- generation: Number.isSafeInteger(generation) ? generation : null,
136
+ generation: Number.isSafeInteger(generation) ? generation : null, provenance: "measured",
130
137
  revision: captures.at(-1).revision ?? null, samples: captures, validity: "valid", unknownReason: null };
131
138
  }
132
139
 
140
+ export function trackRateReadings(readings, { predicted, projected }, now, playbackReadiness) {
141
+ const measured = (readings ?? []).filter(reading => reading.at <= now);
142
+ const summary = measured.length > 0 ? playbackReadiness.rateSummaryOf(measured) : null;
143
+ if (summary) return { summary, captures: measured.map(reading => ({ owner: reading.captureOwner,
144
+ clock: reading.captureClock, clockEpoch: reading.captureClockEpoch,
145
+ startedAt: reading.captureStartedAtMonotonic, completedAt: reading.captureCompletedAtMonotonic,
146
+ revision: reading.at,
147
+ validity: reading.captureOwner === "proxy-encoder" && reading.captureClock === "performance" &&
148
+ Number.isFinite(reading.captureClockEpoch) && Number.isFinite(reading.captureStartedAtMonotonic) &&
149
+ Number.isFinite(reading.captureCompletedAtMonotonic) && Number.isFinite(reading.at) &&
150
+ reading.captureCompletedAtMonotonic >= reading.captureStartedAtMonotonic
151
+ ? "valid" : "unknown",
152
+ unknownReason: reading.captureOwner === "proxy-encoder" && reading.captureClock === "performance" &&
153
+ Number.isFinite(reading.captureClockEpoch) && Number.isFinite(reading.captureStartedAtMonotonic) &&
154
+ Number.isFinite(reading.captureCompletedAtMonotonic) && Number.isFinite(reading.at) &&
155
+ reading.captureCompletedAtMonotonic >= reading.captureStartedAtMonotonic
156
+ ? null : "encoder-sample-capture-unavailable" })) };
157
+ const rate = Number(predicted ?? projected);
158
+ if (Number.isFinite(rate) && rate > 0) {
159
+ const seed = new playbackReadiness.RateTrend();
160
+ seed.add(now, rate);
161
+ const source = predicted === null || predicted === undefined
162
+ ? "current-cost-projection" : "stored-encode-prediction";
163
+ return { summary: seed.snapshot(), captures: [{ owner: "proxy-encoder", clock: "performance",
164
+ clockEpoch: null, startedAt: null, completedAt: null, revision: null, provenance: "projected", source,
165
+ validity: "unknown", unknownReason: "projected-encoder-speed-has-no-capture" }] };
166
+ }
167
+ return null;
168
+ }
169
+
133
170
  function observeForecastInput(observeWait, sourceKey, state, event) {
134
171
  if (!state) return;
135
172
  observeWait?.(sourceKey, "process", {
@@ -956,6 +993,8 @@ export class ViewerRequests {
956
993
  const liveRuns = this.#host.encodeRuns.liveRunsOf?.(output) ?? [];
957
994
  const encoderCapture = encoderCaptureOf(trackRate?.captures, liveRuns, actionId, generation);
958
995
  const encoderCaptureValid = encoderCapture.validity === "valid";
996
+ const projectedRateCanSeedForecast = trackRate?.captures?.length > 0 &&
997
+ trackRate.captures.every(capture => capture.provenance === "projected");
959
998
  const processingStarted = liveRuns.some(({ progress }) => progress.processedSeconds > progress.startPositionSeconds);
960
999
  const startedAt = liveRuns.find(({ startedAt }) => startedAt > 0)?.startedAt ?? now;
961
1000
  tracks.push({
@@ -995,7 +1034,10 @@ export class ViewerRequests {
995
1034
  this.#host.initialOutputBitsPerMediaSecondOf?.(output) ?? 0,
996
1035
  durationSeconds: output.timeline?.publishedStartOf(output.timeline.segmentCount) ?? session.file.durationSeconds,
997
1036
  segmentCount: output.timeline?.segmentCount ?? segments.length,
998
- readings: encoderCaptureValid ? trackRates : [],
1037
+ // A projection is explicitly not a measured capture, but remains the
1038
+ // numeric seed it was before capture provenance was carried here.
1039
+ // Other invalid measured captures stay out of the forecast.
1040
+ readings: encoderCaptureValid || projectedRateCanSeedForecast ? trackRates : [],
999
1041
  capture: encoderCapture,
1000
1042
  segments,
1001
1043
  tailSegments: forecastGeometry?.tailSegments ?? [],
@@ -1247,39 +1289,16 @@ export class ViewerRequests {
1247
1289
  * would drop the later-told, earlier-taken one (torrent-tv/meta#178).
1248
1290
  */
1249
1291
  #trackRateReadings(output, now) {
1250
- const readings = (this.#host.encodeSpeedReadingsOf?.(output) ?? []).filter(reading => reading.at <= now);
1251
- const summary = readings.length > 0 ? this.#host.playbackReadiness.rateSummaryOf(readings) : null;
1252
- if (summary) return { summary, captures: readings.map(reading => ({ owner: reading.captureOwner,
1253
- clock: reading.captureClock, clockEpoch: reading.captureClockEpoch,
1254
- startedAt: reading.captureStartedAtMonotonic, completedAt: reading.captureCompletedAtMonotonic,
1255
- revision: reading.at,
1256
- validity: reading.captureOwner === "proxy-encoder" && reading.captureClock === "performance" &&
1257
- Number.isFinite(reading.captureClockEpoch) && Number.isFinite(reading.captureStartedAtMonotonic) &&
1258
- Number.isFinite(reading.captureCompletedAtMonotonic) && Number.isFinite(reading.at) &&
1259
- reading.captureCompletedAtMonotonic >= reading.captureStartedAtMonotonic
1260
- ? "valid" : "unknown",
1261
- unknownReason: reading.captureOwner === "proxy-encoder" && reading.captureClock === "performance" &&
1262
- Number.isFinite(reading.captureClockEpoch) && Number.isFinite(reading.captureStartedAtMonotonic) &&
1263
- Number.isFinite(reading.captureCompletedAtMonotonic) && Number.isFinite(reading.at) &&
1264
- reading.captureCompletedAtMonotonic >= reading.captureStartedAtMonotonic
1265
- ? null : "encoder-sample-capture-unavailable" })) };
1292
+ const readings = this.#host.encodeSpeedReadingsOf?.(output) ?? [];
1266
1293
  // ONLY UNTIL A PIECE IS MEASURED does the prediction stand for the
1267
1294
  // encoder, and it is not kept among the readings. Added on every poll
1268
1295
  // between runs, it put the machine's share of that moment — 1 % at a
1269
1296
  // cold start — on the whole film, and the forecast read 0.03x
1270
1297
  // (torrent-tv/meta#178).
1271
- const projected = Number(this.#host.predictedEncodeSpeedOf?.(output) ?? this.#host.projectedEncodeSpeedOf?.(output));
1272
- if (Number.isFinite(projected) && projected > 0) {
1273
- const seed = new this.#host.playbackReadiness.RateTrend();
1274
- seed.add(now, projected);
1275
- return { summary: seed.snapshot(), captures: [{ owner: "proxy-encoder", clock: "performance",
1276
- clockEpoch: performance.timeOrigin, startedAt: now, completedAt: now, revision: now,
1277
- validity: "valid", unknownReason: null }] };
1278
- }
1279
- // Progress over wall time includes source waits, which the forecast prices
1280
- // separately. Keep the rate unknown until a measured or calibrated
1281
- // processing rate exists; single-piece runs now publish their own rate.
1282
- return null;
1298
+ return trackRateReadings(readings, {
1299
+ predicted: this.#host.predictedEncodeSpeedOf?.(output),
1300
+ projected: this.#host.projectedEncodeSpeedOf?.(output)
1301
+ }, now, this.#host.playbackReadiness);
1283
1302
  }
1284
1303
 
1285
1304
  }