@torrent-tv/proxy 2.105.3 → 2.105.4

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.105.3",
3
+ "version": "2.105.4",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -147,7 +147,8 @@ export class ViewerRequests {
147
147
  const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
148
148
  if (!Number.isFinite(seconds) || seconds < 0) return null;
149
149
  this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
150
- firstRequiredSegmentsSeconds: context.activeFirstRequiredSegmentsSeconds });
150
+ firstRequiredSegmentsSeconds: context.activeFirstRequiredSegmentsSeconds,
151
+ firstRequiredStages: context.activeFirstRequiredStages });
151
152
  return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
152
153
  }
153
154
 
@@ -192,13 +193,19 @@ export class ViewerRequests {
192
193
  const elapsedMs = waitElapsedSince(context, waitElapsedMs);
193
194
  const measuredWorkSeconds = processing.firstRequiredSegmentsSeconds;
194
195
  const hasActiveEstimate = Number.isFinite(measuredWorkSeconds) && measuredWorkSeconds >= 0;
196
+ const stagesChanged = Array.isArray(processing.firstRequiredStages) &&
197
+ JSON.stringify(processing.firstRequiredStages) !== JSON.stringify(context.activeFirstRequiredStages);
195
198
  const activeTotalSeconds = hasActiveEstimate
196
199
  ? elapsedMs / 1000 + measuredWorkSeconds
197
200
  : context.activeTotalSeconds;
198
- const activeEstimateChanged = hasActiveEstimate && activeTotalSeconds !== context.activeTotalSeconds;
201
+ const activeEstimateChanged = hasActiveEstimate &&
202
+ (activeTotalSeconds !== context.activeTotalSeconds || stagesChanged);
199
203
  if (hasActiveEstimate) {
200
204
  context.activeTotalSeconds = activeTotalSeconds;
201
205
  context.activeFirstRequiredSegmentsSeconds = measuredWorkSeconds;
206
+ if (Array.isArray(processing.firstRequiredStages)) {
207
+ context.activeFirstRequiredStages = processing.firstRequiredStages;
208
+ }
202
209
  }
203
210
  const changed = featuresChanged || activeEstimateChanged;
204
211
  if (sameWait && changed) context.revision += 1;
@@ -213,7 +220,8 @@ export class ViewerRequests {
213
220
  const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
214
221
  if (!Number.isFinite(seconds) || seconds < 0) return null;
215
222
  this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
216
- firstRequiredSegmentsSeconds: hasActiveEstimate ? measuredWorkSeconds : context.activeFirstRequiredSegmentsSeconds });
223
+ firstRequiredSegmentsSeconds: hasActiveEstimate ? measuredWorkSeconds : context.activeFirstRequiredSegmentsSeconds,
224
+ firstRequiredStages: context.activeFirstRequiredStages });
217
225
  return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
218
226
  }
219
227
 
@@ -222,11 +230,12 @@ export class ViewerRequests {
222
230
  return Number.isFinite(estimate) ? { seconds: estimate, stagesMs: null, observations: null } : estimate;
223
231
  }
224
232
 
225
- #logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical, firstRequiredSegmentsSeconds }) {
233
+ #logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical, firstRequiredSegmentsSeconds,
234
+ firstRequiredStages }) {
226
235
  if (context.loggedForecastRevision === context.revision) return;
227
236
  const basis = Number.isFinite(firstRequiredSegmentsSeconds) ? "first-required" : "historical-stages";
228
237
  const estimate = basis === "first-required"
229
- ? { firstRequiredSegmentsSeconds }
238
+ ? { firstRequiredSegmentsSeconds, ...(Array.isArray(firstRequiredStages) ? { firstRequiredStages } : {}) }
230
239
  : { stagesMs: historical?.stagesMs ?? null, observations: historical?.observations ?? null };
231
240
  logger.info(`early wait forecast ${JSON.stringify({ waitId: context.waitId, consumer: context.consumerId,
232
241
  generation: context.generation, features: context.features, elapsedMs, revision: context.revision,
@@ -1062,7 +1071,8 @@ export class ViewerRequests {
1062
1071
  audioOperation: audioOperation?.operation,
1063
1072
  videoSpeedX: videoOperation?.speed,
1064
1073
  audioSpeedX: audioOperation?.speed,
1065
- firstRequiredSegmentsSeconds: forecast.firstRequiredSegmentsSeconds
1074
+ firstRequiredSegmentsSeconds: forecast.firstRequiredSegmentsSeconds,
1075
+ firstRequiredStages: forecast.firstRequiredStages
1066
1076
  } });
1067
1077
  if (waitEstimate) forecast.waitEstimate = waitEstimate;
1068
1078
  }
@@ -1105,6 +1115,7 @@ export class ViewerRequests {
1105
1115
  const holding = forecast.holdingBy;
1106
1116
  this.#host.noteViewerWaiting?.(consumerId || session.id,
1107
1117
  holding?.kind === "production" ? { address: holding.track, index: holding.segment } : null);
1118
+ delete forecast.firstRequiredStages;
1108
1119
  return forecast;
1109
1120
  }
1110
1121
 
@@ -463,23 +463,28 @@ export function predictPlaybackReadiness(input = {}) {
463
463
  // Production accepts a complete held segment input. It cannot overlap its
464
464
  // own source download; reused input retains its original arrival time.
465
465
  const processing = trackState[trackIndex].curve;
466
- let admittedAt = Math.max(trackFinish[trackIndex], sourceArrivalAt);
466
+ let runAdmissionAt = null;
467
+ let runOpenSeconds = 0;
468
+ let productionStartAt = Math.max(trackFinish[trackIndex], sourceArrivalAt);
467
469
  if (!job.produced && encodeWork > 0) {
468
470
  const run = runStarts[trackIndex];
469
471
  const live = run.spans.some(span => segment.startSeconds >= span.start && segment.startSeconds < span.end);
470
472
  if (!live && (run.admittedAt === null || sourceArrivalAt > run.admittedAt)) {
471
- run.admittedAt = admittedAt;
472
- admittedAt += run.openSeconds;
473
+ runAdmissionAt = productionStartAt;
474
+ run.admittedAt = runAdmissionAt;
475
+ runOpenSeconds = run.openSeconds;
476
+ productionStartAt += runOpenSeconds;
473
477
  }
474
478
  }
475
- const producedAt = job.produced || encodeWork === 0 ? 0 : processing.finish(encodeWork, admittedAt);
479
+ const producedAt = job.produced || encodeWork === 0 ? 0 : processing.finish(encodeWork, productionStartAt);
476
480
  trackFinish[trackIndex] = Math.max(trackFinish[trackIndex], producedAt);
477
481
  const sourcePlanned = sourceArrivalAt > 0 && plan.finish >= sourceArrivalAt;
478
482
  productions.push({ at: producedAt, trackIndex, segment, requiresSource: sourceArrivalAt > 0, sourceArrivalAt,
479
- sourcePlanned });
483
+ sourcePlanned, runAdmissionAt, runOpenSeconds, productionStartAt, encodeWork });
480
484
  const deliveryAt = link.finish(sizeBits, Math.max(transferFinish, producedAt));
481
485
  transferFinish = deliveryAt;
482
- completions.push({ at: deliveryAt + finiteNonNegative(track.appendSeconds), trackIndex, segment, sourceArrivalAt,
486
+ const appendSeconds = finiteNonNegative(track.appendSeconds);
487
+ completions.push({ at: deliveryAt + appendSeconds, deliveryAt, appendSeconds, trackIndex, segment, sourceArrivalAt,
483
488
  sourcePlanned });
484
489
  }
485
490
 
@@ -547,13 +552,32 @@ export function predictPlaybackReadiness(input = {}) {
547
552
  ...(stop.unavailableSource ? { unavailableSource: stop.unavailableSource } : {}) };
548
553
  }
549
554
  }
550
- const firstRequiredTrackSeconds = trackState.slice(0, requiredTracks).map((state, trackIndex) => {
555
+ const firstRequiredStages = trackState.slice(0, requiredTracks).map((state, trackIndex) => {
551
556
  const first = state.segments.find(segment => segment.endSeconds > position);
552
- if (!first) return 0;
553
- if (first.ranges.every(range => contains(state.clientRanges, range.start, range.end))) return 0;
557
+ if (!first) return { track: state.track.id ?? trackIndex, segment: null, alreadyBuffered: true,
558
+ sourceArrivalSeconds: 0, runAdmissionSeconds: null, runOpenSeconds: 0, productionStartSeconds: 0,
559
+ productionCompleteSeconds: 0, deliveryCompleteSeconds: 0, appendSeconds: 0, completionSeconds: 0 };
560
+ if (first.ranges.every(range => contains(state.clientRanges, range.start, range.end))) {
561
+ return { track: state.track.id ?? trackIndex, segment: first.index, alreadyBuffered: true,
562
+ sourceArrivalSeconds: 0, runAdmissionSeconds: null, runOpenSeconds: 0, productionStartSeconds: 0,
563
+ productionCompleteSeconds: 0, deliveryCompleteSeconds: 0, appendSeconds: 0, completionSeconds: 0 };
564
+ }
554
565
  const completion = completions.find(item => item.trackIndex === trackIndex && item.segment.index === first.index);
555
- return completion?.at ?? null;
566
+ if (!completion) return { track: state.track.id ?? trackIndex, segment: first.index, alreadyBuffered: false,
567
+ sourceArrivalSeconds: null, runAdmissionSeconds: null, runOpenSeconds: null, productionStartSeconds: null,
568
+ productionCompleteSeconds: null, deliveryCompleteSeconds: null, appendSeconds: null, completionSeconds: null };
569
+ const production = productions.find(item => item.trackIndex === trackIndex && item.segment.index === first.index);
570
+ return { track: state.track.id ?? trackIndex, segment: first.index, alreadyBuffered: false,
571
+ sourceArrivalSeconds: production?.sourceArrivalAt ?? 0,
572
+ runAdmissionSeconds: production?.runAdmissionAt ?? null,
573
+ runOpenSeconds: production?.runOpenSeconds ?? 0,
574
+ productionStartSeconds: production?.productionStartAt ?? null,
575
+ productionCompleteSeconds: production?.at ?? null,
576
+ deliveryCompleteSeconds: completion.deliveryAt,
577
+ appendSeconds: completion.appendSeconds,
578
+ completionSeconds: completion.at };
556
579
  });
580
+ const firstRequiredTrackSeconds = firstRequiredStages.map(stage => stage.completionSeconds);
557
581
  const firstRequiredSegmentsSeconds = firstRequiredTrackSeconds.every(Number.isFinite)
558
582
  ? Math.max(0, ...firstRequiredTrackSeconds) : null;
559
583
  const delay = Math.max(0, ...constraints);
@@ -564,6 +588,7 @@ export function predictPlaybackReadiness(input = {}) {
564
588
  Number.isFinite(delay) ? 'minimum-safe-delay' : 'service-not-advancing', preparedSegments);
565
589
  return { ...forecast, measuredAt: now, predictedStartAt: Number.isFinite(delay) ? now + delay * 1000 : null,
566
590
  firstRequiredSegmentsSeconds,
591
+ firstRequiredStages,
567
592
  // The piece whose `kind` — its production, or its delivery to the page —
568
593
  // comes `atSeconds` from now while the media before it lasts
569
594
  // `aheadSeconds`: the difference is the delay.