@torrent-tv/proxy 2.101.25 → 2.101.26

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.25",
3
+ "version": "2.101.26",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -75,10 +75,15 @@ export async function handleApiSourceFilesGet(req, reply, { sourceRegistry, torr
75
75
  if (consumerId) viewers?.visibleFiles(consumerId, sourceKey, contents.items.map(item => item.fileIndex));
76
76
  const generationRaw = typeof req.query?.generation === "string" ? req.query.generation : "";
77
77
  const generation = generationRaw !== "" && /^\d+$/.test(generationRaw) ? Number(generationRaw) : null;
78
+ const linkMbps = Number(req.query?.linkMbps);
79
+ const linkSampleAgeMs = Number(req.query?.linkSampleAgeMs);
80
+ const earlyLink = Number.isFinite(linkMbps) && linkMbps > 0
81
+ ? { linkMbps, linkSampleAgeMs: Number.isFinite(linkSampleAgeMs) && linkSampleAgeMs >= 0 ? linkSampleAgeMs : null }
82
+ : {};
78
83
  const files = contents.files().map(file => {
79
84
  if (!consumerId || !Number.isSafeInteger(generation) || file.kind !== "video" || file.excludedReason) return file;
80
85
  const waitEstimate = viewerRequests?.waitEstimateForFile?.({ consumerId, generation, sourceKey,
81
- fileIndex: file.fileIndex, fileName: file.relativePath ?? file.name, fileLength: file.length }) ?? null;
86
+ fileIndex: file.fileIndex, fileName: file.relativePath ?? file.name, fileLength: file.length, ...earlyLink }) ?? null;
82
87
  return waitEstimate ? { ...file, waitEstimate } : file;
83
88
  });
84
89
  return reply.send({
@@ -37,9 +37,14 @@ export async function handleApiSourceStatsGet(req, reply, { sourceRegistry, torr
37
37
  const requestedFileName = typeof req.query.fileName === "string" ? req.query.fileName : "";
38
38
  const requestedFileLength = typeof req.query.fileLength === "string" && /^\d+$/.test(req.query.fileLength)
39
39
  ? Number(req.query.fileLength) : null;
40
+ const linkMbps = Number(req.query.linkMbps);
41
+ const linkSampleAgeMs = Number(req.query.linkSampleAgeMs);
42
+ const earlyLink = Number.isFinite(linkMbps) && linkMbps > 0
43
+ ? { linkMbps, linkSampleAgeMs: Number.isFinite(linkSampleAgeMs) && linkSampleAgeMs >= 0 ? linkSampleAgeMs : null }
44
+ : {};
40
45
  const initialEstimate = () => consumerId && Number.isSafeInteger(generation) && Number.isSafeInteger(fileIndex)
41
46
  ? viewerRequests?.waitEstimateForFile?.({ consumerId, generation, sourceKey, fileIndex,
42
- fileName: requestedFileName, fileLength: requestedFileLength }) ?? null
47
+ fileName: requestedFileName, fileLength: requestedFileLength, ...earlyLink }) ?? null
43
48
  : null;
44
49
 
45
50
  let torrent;
@@ -137,7 +142,7 @@ export async function handleApiSourceStatsGet(req, reply, { sourceRegistry, torr
137
142
 
138
143
  const waitEstimate = consumerId && Number.isSafeInteger(generation)
139
144
  ? viewerRequests?.waitEstimateForSource?.({ consumerId, generation, sourceKey, fileIndex,
140
- fileName: torrent.files?.[fileIndex]?.name ?? requestedFileName, fileLength: requestedFileLength, stats }) ?? null
145
+ fileName: torrent.files?.[fileIndex]?.name ?? requestedFileName, fileLength: requestedFileLength, stats, ...earlyLink }) ?? null
141
146
  : null;
142
147
  return reply.send(waitEstimate ? { ...stats, waitEstimate } : stats);
143
148
  }
@@ -280,6 +280,7 @@ export class LocalObservations {
280
280
  fileLength: positive(features.fileLength),
281
281
  downloadSpeed: nonNegative(features.downloadSpeed),
282
282
  linkMbps: nonNegative(features.linkMbps),
283
+ linkSampleAgeMs: nonNegative(features.linkSampleAgeMs),
283
284
  headerRemaining: nonNegative(features.headerRemaining),
284
285
  peers: nonNegative(features.peers),
285
286
  at: this.#now()
@@ -302,7 +303,7 @@ export class LocalObservations {
302
303
  const readings = this.#entries.firstFrame[this.#firstFrameKey()] ?? [];
303
304
  const candidates = readings.filter(reading => reading.extension === (features.extension ?? ""));
304
305
  if (candidates.length === 0) return null;
305
- const dimensions = ["fileLength", "downloadSpeed", "linkMbps", "headerRemaining", "peers"];
306
+ const dimensions = ["fileLength", "downloadSpeed", "linkMbps", "linkSampleAgeMs", "headerRemaining", "peers"];
306
307
  const scales = new Map(dimensions.map(key => [key, median(candidates
307
308
  .map(reading => nonNegative(reading[key]))
308
309
  .filter(value => value !== null && value > 0))]));
@@ -106,7 +106,7 @@ export class ViewerRequests {
106
106
  * @param {{ consumerId: string, generation: number, sourceKey: string, fileIndex: number, fileName: string, fileLength: number }} params
107
107
  * @returns {object | null}
108
108
  */
109
- waitEstimateForFile({ consumerId, generation, sourceKey, fileIndex, fileName, fileLength }) {
109
+ waitEstimateForFile({ consumerId, generation, sourceKey, fileIndex, fileName, fileLength, linkMbps, linkSampleAgeMs }) {
110
110
  if (!consumerId || !Number.isSafeInteger(generation) || typeof sourceKey !== "string" ||
111
111
  !Number.isSafeInteger(fileIndex) || fileIndex < 0) return null;
112
112
  const waitId = `${sourceKey}:${fileIndex}:${generation}`;
@@ -116,7 +116,9 @@ export class ViewerRequests {
116
116
  extension,
117
117
  fileLength,
118
118
  downloadSpeed: null,
119
- linkMbps: this.#host.viewers.get(consumerId)?.linkReading()?.linkMbps,
119
+ linkMbps: Number.isFinite(linkMbps) && linkMbps > 0
120
+ ? linkMbps : this.#host.viewers.get(consumerId)?.linkReading()?.linkMbps,
121
+ linkSampleAgeMs: Number.isFinite(linkSampleAgeMs) && linkSampleAgeMs >= 0 ? linkSampleAgeMs : null,
120
122
  headerRemaining: null,
121
123
  peers: null
122
124
  };
@@ -144,7 +146,8 @@ export class ViewerRequests {
144
146
  * @param {{ consumerId: string, generation: number, sourceKey: string, fileIndex: number | null, fileName?: string, fileLength?: number | null, stats?: object }} params
145
147
  * @returns {object | null}
146
148
  */
147
- waitEstimateForSource({ consumerId, generation, sourceKey, fileIndex, fileName = "", fileLength = null, stats = {} }) {
149
+ waitEstimateForSource({ consumerId, generation, sourceKey, fileIndex, fileName = "", fileLength = null,
150
+ stats = {}, linkMbps, linkSampleAgeMs }) {
148
151
  if (!consumerId || !Number.isSafeInteger(generation) || typeof sourceKey !== "string") return null;
149
152
  const waitId = `${sourceKey}:${fileIndex ?? "all"}:${generation}`;
150
153
  const key = `${consumerId}:${waitId}`;
@@ -154,7 +157,9 @@ export class ViewerRequests {
154
157
  fileLength: Number.isFinite(stats?.fileLength) && stats.fileLength > 0
155
158
  ? stats.fileLength : fileLength,
156
159
  downloadSpeed: stats?.downloadSpeed,
157
- linkMbps: this.#host.viewers.get(consumerId)?.linkReading()?.linkMbps,
160
+ linkMbps: Number.isFinite(linkMbps) && linkMbps > 0
161
+ ? linkMbps : this.#host.viewers.get(consumerId)?.linkReading()?.linkMbps,
162
+ linkSampleAgeMs: Number.isFinite(linkSampleAgeMs) && linkSampleAgeMs >= 0 ? linkSampleAgeMs : null,
158
163
  headerRemaining: Number.isFinite(stats?.headerBytes) && Number.isFinite(stats?.headerDownloadedBytes)
159
164
  ? Math.max(0, stats.headerBytes - stats.headerDownloadedBytes) : null,
160
165
  peers: stats?.connectedPeers ?? stats?.numPeers