@torrent-tv/proxy 2.55.2 → 2.55.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/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
## 2.55.
|
|
1
|
+
## 2.55.4
|
|
2
|
+
|
|
3
|
+
- **Fix**: A file's subtitle cues are now walked the moment a piece verifies, not on a 3 s poll. The poll (2.55.3) closed the worst of it but still left every new cluster waiting up to 3 s after its piece arrived, and "waiting" at all was the thing objected to — a cue's readiness must not depend on which of two independent timers happens to fire first. `torrent.on("verified", …)` is WebTorrent's own signal for exactly this instant, set in the same place the bitfield itself is (`_markVerified`), so the walk now runs off the same event that makes a piece a piece rather than off a schedule. The 3 s poll stays as a fallback — it only matters for a listener attached after some pieces already verified, or if a `verified` handler ever throws — so nothing that used to be caught can now be missed.
|
|
4
|
+
|
|
5
|
+
- **Fix**: A track's subtitle cues are now walked ahead of being asked for, instead of only when a browser first requests them. `cuesHeldFor` only ever read clusters on demand, inside the `/api/subtitles` request itself — cheap once caught up, but the FIRST call for a track had to walk the whole backlog of already-downloaded-but-unparsed clusters serially, with no `pending`/streaming pattern the way the ffmpeg fallback has one. On a film well into playback that backlog is not small (`Minions.and.Monsters.1080p.mkv` indexes over a thousand cluster positions per track), so a viewer who turned subtitles on after watching for a while waited on that catch-up instead of seeing cues appear at once — the opposite of the rule this file states its own reason for existing ("the region the viewer is watching is downloaded before they reach it, so its cues are ready before they are needed"): true of the DATA, not of when it got READ. A new periodic pass in the torrent worker (`warmSubtitleCues`, every 3 s, one per actively-read file) walks new clusters as they arrive, reusing `cuesHeldFor`'s own memoized state — a file nobody has opened costs nothing, and a file being watched is caught up by the time a track is switched on.
|
|
2
6
|
|
|
3
7
|
- **Fix**: The decode pipe's sanity log carried a fixed editorial line — "a reading where these are far apart is worth a second look" — printed on every single reading regardless of whether the two figures actually were, which was noise the first time it ran in the field. The comparison is also now taken over the same window the speed itself is (bytes are snapshotted alongside each progress sample), rather than over the whole run from process start, which read systematically low for no reason but that mismatch. The line only says "worth a second look" when the two figures are actually more than 1.5x apart.
|
|
4
8
|
|
package/package.json
CHANGED
|
@@ -316,6 +316,29 @@ export async function cuesHeldFor(torrent, fileIndex, sourceKey, trackNumber) {
|
|
|
316
316
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Walk whatever clusters have newly arrived, for every text track a file
|
|
321
|
+
* carries — so the browser's first request for a track finds its cues already
|
|
322
|
+
* caught up instead of paying for the whole backlog inside that request.
|
|
323
|
+
*
|
|
324
|
+
* `cuesHeldFor` already skips positions it has walked before (`state.walked`),
|
|
325
|
+
* so calling this on a timer is cheap once a file is caught up: the only cost
|
|
326
|
+
* is deciding there is nothing new to read. It is `getSubtitleCues` run ahead
|
|
327
|
+
* of being asked, on the same state that call itself would build — nothing is
|
|
328
|
+
* duplicated, and a browser that never asks costs nothing beyond this.
|
|
329
|
+
*
|
|
330
|
+
* @param {object} torrent
|
|
331
|
+
* @param {number} fileIndex
|
|
332
|
+
* @param {string} sourceKey
|
|
333
|
+
* @returns {Promise<void>}
|
|
334
|
+
*/
|
|
335
|
+
export async function warmSubtitleCues(torrent, fileIndex, sourceKey) {
|
|
336
|
+
const plan = await planFor(torrent, fileIndex, `${sourceKey}:${fileIndex}`);
|
|
337
|
+
for (const track of plan?.tracks ?? []) {
|
|
338
|
+
await cuesHeldFor(torrent, fileIndex, sourceKey, track.trackNumber);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
319
342
|
/**
|
|
320
343
|
* The text subtitle tracks of a file, for the menu the viewer sees.
|
|
321
344
|
*
|
|
@@ -27,7 +27,7 @@ import { parentPort, workerData } from "node:worker_threads";
|
|
|
27
27
|
import { createSendStream } from "./channel.js";
|
|
28
28
|
import { createFileClaims } from "./file-claims.js";
|
|
29
29
|
import { readFragments, supplyFiguresFor } from "./piece-reader.js";
|
|
30
|
-
import { cuesHeldFor, declaredSubtitleTracksOf, subtitleTracksOf } from "./subtitle-cues.js";
|
|
30
|
+
import { cuesHeldFor, declaredSubtitleTracksOf, subtitleTracksOf, warmSubtitleCues } from "./subtitle-cues.js";
|
|
31
31
|
import { Command, Event } from "./protocol.js";
|
|
32
32
|
|
|
33
33
|
// Imported dynamically, and that is load-bearing: static imports are RESOLVED
|
|
@@ -507,4 +507,68 @@ setInterval(() => {
|
|
|
507
507
|
}
|
|
508
508
|
}, STORE_REPORT_INTERVAL_MS).unref();
|
|
509
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Walk subtitle cues for every actively-read file of one torrent.
|
|
512
|
+
*
|
|
513
|
+
* @param {string} sourceKey
|
|
514
|
+
* @param {object} torrent
|
|
515
|
+
* @returns {void}
|
|
516
|
+
*/
|
|
517
|
+
function warmActiveFiles(sourceKey, torrent) {
|
|
518
|
+
const usage = pool.fileUsageByTorrent.get(torrent);
|
|
519
|
+
if (!usage) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
for (const fileIndex of usage.keys()) {
|
|
523
|
+
warmSubtitleCues(torrent, fileIndex, sourceKey).catch((error) => {
|
|
524
|
+
log(`subtitle warmup ${sourceKey}:${fileIndex} failed: ${error instanceof Error ? error.message : error}`);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Torrents already wired to warm their subtitle cues the moment a piece
|
|
531
|
+
* verifies, so the same torrent is not listened to twice.
|
|
532
|
+
*
|
|
533
|
+
* @type {WeakSet<object>}
|
|
534
|
+
*/
|
|
535
|
+
const subtitleWarmupWired = new WeakSet();
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* A piece becoming readable is the actual event a cue can be pulled from —
|
|
539
|
+
* "downloaded", not "about to be encoded or copied": what a viewer reaches is
|
|
540
|
+
* decided by the read window ahead of the playhead, not by which of the two
|
|
541
|
+
* paths a segment takes, and the piece exists (and is worth reading for
|
|
542
|
+
* subtitles) whichever one that is. `verified` is WebTorrent's own signal for
|
|
543
|
+
* exactly that instant, set at the same place the bitfield itself is (`
|
|
544
|
+
* _markVerified`), so nothing here is guessing at readiness a different way.
|
|
545
|
+
*
|
|
546
|
+
* @param {string} sourceKey
|
|
547
|
+
* @param {object} torrent
|
|
548
|
+
* @returns {void}
|
|
549
|
+
*/
|
|
550
|
+
function ensureSubtitleWarmupWired(sourceKey, torrent) {
|
|
551
|
+
if (subtitleWarmupWired.has(torrent)) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
subtitleWarmupWired.add(torrent);
|
|
555
|
+
torrent.on("verified", () => warmActiveFiles(sourceKey, torrent));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* How often an actively-read file's subtitle cues are walked ahead of being
|
|
560
|
+
* asked for, as a fallback beside the per-piece `verified` listener above —
|
|
561
|
+
* catches a listener attached after pieces already verified, and anything the
|
|
562
|
+
* event path might otherwise miss. Cheap once caught up (`warmSubtitleCues`
|
|
563
|
+
* skips clusters it has already read).
|
|
564
|
+
*/
|
|
565
|
+
const SUBTITLE_WARMUP_INTERVAL_MS = 3_000;
|
|
566
|
+
|
|
567
|
+
setInterval(() => {
|
|
568
|
+
for (const [sourceKey, torrent] of pool.torrents) {
|
|
569
|
+
ensureSubtitleWarmupWired(sourceKey, torrent);
|
|
570
|
+
warmActiveFiles(sourceKey, torrent);
|
|
571
|
+
}
|
|
572
|
+
}, SUBTITLE_WARMUP_INTERVAL_MS).unref();
|
|
573
|
+
|
|
510
574
|
log("torrent worker started");
|