@torrent-tv/proxy 2.101.33 → 2.101.35

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.101.33",
3
+ "version": "2.101.35",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -290,10 +290,13 @@ export class LocalObservations {
290
290
  }
291
291
 
292
292
  /**
293
- * Price the early wait from separate measured stage medians for the same
294
- * output path. Numeric proximity of a past file, source rate, or link rate
295
- * does not select a completed run. Each observation contributes once, using
296
- * its latest snapshot that states the current cache and operation path.
293
+ * Price the early wait from separate measured stage medians on this proxy
294
+ * configuration. Before the container and track operations are known, use
295
+ * measurements from every observed file type; once current cache and
296
+ * operation facts are known, retain only observations that match those
297
+ * facts. Numeric proximity to a past file, source rate, or link rate never
298
+ * selects a completed run. Each observation contributes once, using its
299
+ * latest snapshot that states the current cache and operation path.
297
300
  *
298
301
  * @param {object} features
299
302
  * @returns {number | null}
@@ -303,7 +306,7 @@ export class LocalObservations {
303
306
  const stages = ["transportMs", "planMs", "prepareMs", "prebufferMs", "presentationMs"];
304
307
  const candidates = [];
305
308
  for (const reading of this.#entries.firstFrame[this.#firstFrameKey()] ?? []) {
306
- if (reading.extension !== (features.extension ?? "") || !reading.stages) continue;
309
+ if (!reading.stages) continue;
307
310
  const points = Array.isArray(reading.featureSnapshots) && reading.featureSnapshots.length > 0
308
311
  ? reading.featureSnapshots : [reading];
309
312
  const point = [...points].reverse().find(candidate => firstFramePathMatches(features, candidate));
@@ -1031,48 +1031,6 @@ export class SegmentServing {
1031
1031
  }
1032
1032
  }
1033
1033
 
1034
- /**
1035
- * Wait for a valid HLS playlist or a terminal output state, without polling.
1036
- *
1037
- * @param {HlsSession} session
1038
- * @returns {Promise<void>}
1039
- */
1040
- async waitUntilReady(session) {
1041
- // With a synthetic VOD playlist there is nothing to wait for: the playlist
1042
- // is generated from the probed duration and is available immediately.
1043
- // Individual segments are long-polled by the segment route as ffmpeg
1044
- // produces them.
1045
- if (session.useSyntheticPlaylist) {
1046
- if (this.#host.encodeRuns.hasFailed(session)) {
1047
- throw new Error(this.#host.encodeRuns.lastErrorOf(session) || "ffmpeg failed to start HLS session.");
1048
- }
1049
- return;
1050
- }
1051
-
1052
- const playlistPath = path.join(this.#host.segmentStore.pathFor(session.outputKey ?? ""), PLAYLIST_FILE_NAME);
1053
- while (this.#host.outputs.get(session.id) === session) {
1054
- const change = this.subscribeFileChange(session.id);
1055
- try {
1056
- if (this.#host.encodeRuns.hasFailed(session)) {
1057
- throw new Error(this.#host.encodeRuns.lastErrorOf(session) || "ffmpeg failed to start HLS session.");
1058
- }
1059
- try {
1060
- await access(playlistPath);
1061
- const text = await readFile(playlistPath, "utf8");
1062
- if (text.includes("#EXTM3U")) {
1063
- return;
1064
- }
1065
- } catch (_error) {
1066
- // Playlist is not ready yet.
1067
- }
1068
- await change.changed;
1069
- } finally {
1070
- change.release();
1071
- }
1072
- }
1073
- throw new Error("HLS output was disposed before its playlist became available.");
1074
- }
1075
-
1076
1034
  /**
1077
1035
  * Answer the player's report that a delivered fragment sits far from the edge
1078
1036
  * of its buffer, with the one fact only this side holds: which boundary the
@@ -11,13 +11,6 @@ import { logger } from "../../utils/logger.js";
11
11
  import { isOutputName, OutputSpec } from "../encode/output/index.js";
12
12
  import { withSubtitleReadiness } from "./subtitle-readiness.js";
13
13
 
14
- function isWarmupTimeoutError(error) {
15
- if (!(error instanceof Error)) {
16
- return false;
17
- }
18
- return error.message === "HLS playlist is still warming up.";
19
- }
20
-
21
14
  /**
22
15
  * The segment number an assignment is recorded under for this file, −1 for the
23
16
  * init, or null for anything that is neither (a playlist).
@@ -93,7 +86,7 @@ export class ViewerRequests {
93
86
  #forecastPieces = new WeakMap();
94
87
 
95
88
  /**
96
- * @param {object} host - `opening`, `activeOutputFor`, `viewerSecondsOn`, `minimumBufferSecondsFor`, `getSourceStats`, `disposeSession`, `planEncodersSoon`, `waitUntilReady`, `encodeRuns`, `encodeSpeedReadingsOf`, `outputTimes`, `outputs`, `lookaheadSeconds`, `quality`, `qualityOffer`, `renditions`, `segmentDurationSec`, `segmentStore`, `sourceFiles`, `startupWaitMs`, `viewers`, `acceptsGeneration`, `generationOfRequest`, `noteGivenOutput`, `holdForResponse`, `noteServingVerdict`, `servingVerdictOf`
89
+ * @param {object} host - `opening`, `activeOutputFor`, `viewerSecondsOn`, `minimumBufferSecondsFor`, `getSourceStats`, `planEncodersSoon`, `encodeRuns`, `encodeSpeedReadingsOf`, `outputTimes`, `outputs`, `lookaheadSeconds`, `quality`, `qualityOffer`, `renditions`, `segmentDurationSec`, `segmentStore`, `sourceFiles`, `startupWaitMs`, `viewers`, `acceptsGeneration`, `generationOfRequest`, `noteGivenOutput`, `holdForResponse`, `noteServingVerdict`, `servingVerdictOf`
97
90
  */
98
91
  constructor(host) {
99
92
  this.#host = host;
@@ -421,19 +414,13 @@ export class ViewerRequests {
421
414
  // Where the first encoder goes is the plan's, from where the viewers stand.
422
415
  this.#host.planEncodersSoon();
423
416
  }
424
- try {
425
- await this.#host.waitUntilReady(output);
426
- } catch (error) {
427
- if (!existed && this.#host.encodeRuns.hasFailed(output)) {
428
- await this.#host.disposeSession(output.id);
429
- throw error;
430
- }
431
- if (existed && !isWarmupTimeoutError(error)) {
432
- throw error;
433
- }
434
- // Not a failure of the request: the synthetic playlist is already there
435
- // and segments appear as the encoder produces them.
436
- }
417
+ // Return the allocated session before a keyframe-based output writes its
418
+ // first playlist. The browser can then poll this session's live playback
419
+ // forecast while the source, proxy and client-link work is still underway;
420
+ // otherwise this request hides all revised estimates until the first
421
+ // segment is already ready. The playlist route and the viewer's existing
422
+ // wait loop handle output that is still being prepared, and the progress
423
+ // route reports a terminal encoder failure.
437
424
  return output;
438
425
  }
439
426
 
@@ -631,7 +631,6 @@ export function wireOutputs({
631
631
  getSourceStats: (...args) => parts.getSourceStats?.(...args) ?? null,
632
632
  disposeSession: (...args) => parts.lifecycle.disposeSession(...args),
633
633
  planEncodersSoon: (...args) => parts.encodeRuns.planEncodersSoon(...args),
634
- waitUntilReady: (...args) => parts.serving.waitUntilReady(...args),
635
634
  noteViewerWaiting: (viewer, wait) => parts.encodeOrchestrator?.noteViewerWaiting(viewer, wait),
636
635
  get encodeRuns() { return parts.encodeRuns; },
637
636
  // When this viewer's playback can start and run to the end: the viewer