@torrent-tv/proxy 2.98.5 → 2.98.7
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
package/server.js
CHANGED
|
@@ -200,7 +200,7 @@ export async function startProxyServer({
|
|
|
200
200
|
// The ONE forward reference here, and it is safe by construction: this fires
|
|
201
201
|
// only once the worker thread is up and a piece has verified, which is long
|
|
202
202
|
// after the declarations below have run.
|
|
203
|
-
onPiecesArrived: ({ sourceKey, fileIndexes }) => {
|
|
203
|
+
onPiecesArrived: ({ sourceKey, fileIndexes, arrived }) => {
|
|
204
204
|
for (const fileIndex of fileIndexes) {
|
|
205
205
|
mediaReads.bytesChanged(sourceKey, fileIndex);
|
|
206
206
|
probeReads.bytesChanged(sourceKey, fileIndex);
|
|
@@ -211,7 +211,9 @@ export async function startProxyServer({
|
|
|
211
211
|
// is read again now; nothing was recorded for that wait.
|
|
212
212
|
keyframeTables.readAgainIfUnanswered({ sourceKey, fileIndex, logName: String(fileIndex) });
|
|
213
213
|
}
|
|
214
|
-
|
|
214
|
+
// Which bytes arrived, when the event names them: an input waiting for
|
|
215
|
+
// named bytes is tried again only when some of them have come.
|
|
216
|
+
outputParts.encodeInputs?.bytesChanged(Array.isArray(arrived) ? { sourceKey, ranges: arrived } : null);
|
|
215
217
|
}
|
|
216
218
|
});
|
|
217
219
|
const selectedPort = await getPort({
|
|
@@ -259,10 +259,35 @@ export class EncodeInputs {
|
|
|
259
259
|
return this.take(output, from, to);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Bytes of a source changed: every waiting input is tried again, except one
|
|
264
|
+
* that waits for named bytes none of which have come. Tried at every piece
|
|
265
|
+
* of the file, a seek's input was prepared 28 times in 8.5 s on Home
|
|
266
|
+
* Assistant, each time asking the torrent thread which ranges it holds
|
|
267
|
+
* (171 ms on average), and the try that found its bytes could start only
|
|
268
|
+
* after the one in flight (r34, torrent-tv/meta#178).
|
|
269
|
+
*
|
|
270
|
+
* @param {{ sourceKey: string, ranges: Array<{ fileIndex: number, start: number, end: number }> } | null} [arrived] -
|
|
271
|
+
* The bytes that arrived, inclusive, when the event names them.
|
|
272
|
+
*/
|
|
273
|
+
bytesChanged(arrived = null) {
|
|
263
274
|
this.#revision++;
|
|
264
275
|
for (const request of this.#requests.values()) {
|
|
265
|
-
if (
|
|
276
|
+
if (request.pending) {
|
|
277
|
+
// The try in flight may have read before these bytes came. What it
|
|
278
|
+
// waits for is known only when it answers, so they are kept for that
|
|
279
|
+
// answer; bytes not named make it try again whatever it answers.
|
|
280
|
+
if (!arrived) request.unnamedArrival = true;
|
|
281
|
+
else (request.arrivedMeanwhile ??= []).push(arrived);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (request.result?.kind === "result" || request.result?.kind === "terminal") continue;
|
|
285
|
+
if (arrived && request.result?.kind === "needs-bytes" && !awaitedIn(request.result, arrived)) {
|
|
286
|
+
// Nothing it waits for has come: it stands as tried at this revision.
|
|
287
|
+
request.revision = this.#revision;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
this.#prepare(request);
|
|
266
291
|
}
|
|
267
292
|
}
|
|
268
293
|
|
|
@@ -324,6 +349,8 @@ export class EncodeInputs {
|
|
|
324
349
|
request.pending = true;
|
|
325
350
|
request.revision = this.#revision;
|
|
326
351
|
request.memoryRevision = this.#memoryRevision;
|
|
352
|
+
request.arrivedMeanwhile = null;
|
|
353
|
+
request.unnamedArrival = false;
|
|
327
354
|
// Where the time from the request to its input goes, across every attempt:
|
|
328
355
|
// field 2026-10-10, a picture's request took 38 s before its copy began and
|
|
329
356
|
// nothing said on what (torrent-tv/meta#166).
|
|
@@ -365,12 +392,29 @@ export class EncodeInputs {
|
|
|
365
392
|
request.pending = false;
|
|
366
393
|
if (request.result?.kind !== "needs-memory") this.#wanted.delete(request.key);
|
|
367
394
|
const changed = request.result?.kind === "needs-memory"
|
|
368
|
-
? request.memoryRevision !== this.#memoryRevision :
|
|
395
|
+
? request.memoryRevision !== this.#memoryRevision : this.#arrivedFor(request);
|
|
369
396
|
if (this.#requests.get(request.key) === request && changed &&
|
|
370
397
|
request.result?.kind !== "result" && request.result?.kind !== "terminal") this.#prepare(request);
|
|
371
398
|
});
|
|
372
399
|
}
|
|
373
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Whether bytes came during a try that the try may have missed. A try that
|
|
403
|
+
* waits for named bytes is repeated only when some of them came, or when an
|
|
404
|
+
* event named nothing; the others stand it as tried at this revision. In r36
|
|
405
|
+
* a held-range check took 344 ms, and a piece of the file arriving during it
|
|
406
|
+
* started another (torrent-tv/meta#178).
|
|
407
|
+
*/
|
|
408
|
+
#arrivedFor(request) {
|
|
409
|
+
if (request.revision === this.#revision) return false;
|
|
410
|
+
const arrivals = request.arrivedMeanwhile ?? [];
|
|
411
|
+
request.arrivedMeanwhile = null;
|
|
412
|
+
if (request.unnamedArrival || request.result?.kind !== "needs-bytes" ||
|
|
413
|
+
arrivals.some(arrived => awaitedIn(request.result, arrived))) return true;
|
|
414
|
+
request.revision = this.#revision;
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
374
418
|
/** The reservation a packet input makes: all of it, or nothing. */
|
|
375
419
|
#reserveFor(request) {
|
|
376
420
|
return async bytes => {
|
|
@@ -486,7 +530,9 @@ export class EncodeInputs {
|
|
|
486
530
|
// found pieces missing, and the run was refused for good (torrent-tv/meta#178).
|
|
487
531
|
if (this.#heldRanges !== null && !await this.#present(chosen.sources, held, request.timing)) {
|
|
488
532
|
const [first] = chosen.sources;
|
|
489
|
-
return { kind: "needs-bytes", sourceKey: first.sourceKey, fileIndex: first.fileIndex, ranges: first.input.ranges
|
|
533
|
+
return { kind: "needs-bytes", sourceKey: first.sourceKey, fileIndex: first.fileIndex, ranges: first.input.ranges,
|
|
534
|
+
// Every source of the stretch, so the bytes of any of them wake it.
|
|
535
|
+
awaiting: chosen.sources.map(source => ({ sourceKey: source.sourceKey, fileIndex: source.fileIndex, ranges: source.input.ranges })) };
|
|
490
536
|
}
|
|
491
537
|
if (this.#requests.get(request.key) !== request) return undefined;
|
|
492
538
|
this.#wanted.set(request.key, { wanted: chosen.bytes, required: chosen.bytes });
|
|
@@ -527,6 +573,21 @@ export class EncodeInputs {
|
|
|
527
573
|
}
|
|
528
574
|
}
|
|
529
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Whether some of the bytes an input waits for are among those that arrived.
|
|
578
|
+
*
|
|
579
|
+
* @param {{ sourceKey: string, fileIndex: number, ranges: Array<[number, number]>,
|
|
580
|
+
* awaiting?: Array<{ sourceKey: string, fileIndex: number, ranges: Array<[number, number]> }> }} waiting
|
|
581
|
+
* @param {{ sourceKey: string, ranges: Array<{ fileIndex: number, start: number, end: number }> }} arrived
|
|
582
|
+
* @returns {boolean}
|
|
583
|
+
*/
|
|
584
|
+
function awaitedIn(waiting, arrived) {
|
|
585
|
+
const sources = waiting.awaiting ?? [waiting];
|
|
586
|
+
return sources.some(source => source.sourceKey === arrived.sourceKey && Array.isArray(source.ranges) &&
|
|
587
|
+
arrived.ranges.some(range => range.fileIndex === source.fileIndex &&
|
|
588
|
+
source.ranges.some(([start, end]) => range.start <= end && range.end >= start)));
|
|
589
|
+
}
|
|
590
|
+
|
|
530
591
|
/**
|
|
531
592
|
* Where a request has spent its time since it was asked, as one clause of its
|
|
532
593
|
* log line. The resolutions include the reads of the file they made, and those
|
|
@@ -1300,6 +1300,7 @@ export class TorrentPool {
|
|
|
1300
1300
|
// depend on facts that are gone by the time it is said.
|
|
1301
1301
|
if (Array.isArray(zones) && zones.length === 0) {
|
|
1302
1302
|
withdrawPriorityMap(torrent, fileIndex);
|
|
1303
|
+
reconcileAll();
|
|
1303
1304
|
// The ends of the file go with it. They are kept for as long as the file
|
|
1304
1305
|
// is open, and this is what says it is not.
|
|
1305
1306
|
// And this may have been the last thing anybody wanted of this torrent.
|
|
@@ -1346,6 +1347,11 @@ export class TorrentPool {
|
|
|
1346
1347
|
// Bands the map no longer has: a viewer moved on, and what they had wanted
|
|
1347
1348
|
// is not wanted by anybody now.
|
|
1348
1349
|
withdrawPriorityMap(torrent, fileIndex, ordered.length);
|
|
1350
|
+
// THE SWARM IS TOLD WHEN THE MAP IS APPLIED, not at the next piece verified
|
|
1351
|
+
// or the pool's timer: on Home Assistant a seek's map was applied and the
|
|
1352
|
+
// swarm went on fetching the old first class for 1.16 s, until the next
|
|
1353
|
+
// piece happened to be verified (r34, torrent-tv/meta#178).
|
|
1354
|
+
reconcileAll();
|
|
1349
1355
|
// WHAT THE SWARM WAS ACTUALLY TOLD, said on change and never on a timer.
|
|
1350
1356
|
// The map was applied in silence: that it had been BUILT was visible in the
|
|
1351
1357
|
// encoding's own line, and that the download had received it was visible
|
|
@@ -254,7 +254,8 @@ export class TorrentWorkerClient {
|
|
|
254
254
|
this.#onPiecesArrived({
|
|
255
255
|
sourceKey: message.sourceKey,
|
|
256
256
|
fileIndexes: Array.isArray(message.fileIndexes) ? message.fileIndexes : [],
|
|
257
|
-
...(typeof message.reason === "string" ? { reason: message.reason } : {})
|
|
257
|
+
...(typeof message.reason === "string" ? { reason: message.reason } : {}),
|
|
258
|
+
...(Array.isArray(message.arrived) ? { arrived: message.arrived } : {})
|
|
258
259
|
});
|
|
259
260
|
break;
|
|
260
261
|
default:
|
|
@@ -881,12 +881,36 @@ function describePieceBuffers() {
|
|
|
881
881
|
* @param {object} torrent
|
|
882
882
|
* @returns {void}
|
|
883
883
|
*/
|
|
884
|
-
function announceArrivals(sourceKey, torrent, reason = "verified") {
|
|
884
|
+
function announceArrivals(sourceKey, torrent, reason = "verified", piece = null) {
|
|
885
885
|
const fileIndexes = [...demandFor(torrent).register.files()];
|
|
886
886
|
if (fileIndexes.length === 0) {
|
|
887
887
|
return;
|
|
888
888
|
}
|
|
889
|
-
parentPort.postMessage({ type: Event.PIECES_CHANGED, sourceKey, fileIndexes, reason
|
|
889
|
+
parentPort.postMessage({ type: Event.PIECES_CHANGED, sourceKey, fileIndexes, reason,
|
|
890
|
+
...(Number.isSafeInteger(piece) ? { arrived: bytesOfPiece(torrent, piece) } : {}) });
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* The bytes of one piece, as each file it covers counts them: what a reader
|
|
895
|
+
* waiting for named bytes compares against, so a piece of another part of the
|
|
896
|
+
* file wakes nobody (torrent-tv/meta#178).
|
|
897
|
+
*
|
|
898
|
+
* @param {object} torrent
|
|
899
|
+
* @param {number} piece
|
|
900
|
+
* @returns {Array<{ fileIndex: number, start: number, end: number }>} Inclusive.
|
|
901
|
+
*/
|
|
902
|
+
function bytesOfPiece(torrent, piece) {
|
|
903
|
+
const pieceLength = Number(torrent.pieceLength);
|
|
904
|
+
const length = Number(torrent.length);
|
|
905
|
+
if (!(pieceLength > 0) || !(length > 0)) return [];
|
|
906
|
+
const start = piece * pieceLength;
|
|
907
|
+
const end = Math.min(length, start + pieceLength) - 1;
|
|
908
|
+
const arrived = [];
|
|
909
|
+
(torrent.files ?? []).forEach((file, fileIndex) => {
|
|
910
|
+
const from = Math.max(start, file.offset), to = Math.min(end, file.offset + file.length - 1);
|
|
911
|
+
if (to >= from) arrived.push({ fileIndex, start: from - file.offset, end: to - file.offset });
|
|
912
|
+
});
|
|
913
|
+
return arrived;
|
|
890
914
|
}
|
|
891
915
|
|
|
892
916
|
|
|
@@ -917,7 +941,7 @@ function ensureArrivalsWired(sourceKey, torrent) {
|
|
|
917
941
|
}
|
|
918
942
|
arrivalsWired.add(torrent);
|
|
919
943
|
holdingsChanged();
|
|
920
|
-
torrent.on("verified", () => announceArrivals(sourceKey, torrent));
|
|
944
|
+
torrent.on("verified", (piece) => announceArrivals(sourceKey, torrent, "verified", piece));
|
|
921
945
|
torrent.on("piece-withdrawn", () => announceArrivals(sourceKey, torrent, "withdrawn"));
|
|
922
946
|
torrent.once("close", () => {
|
|
923
947
|
if (resolvedTorrents.get(sourceKey) !== torrent) return;
|