@torrent-tv/proxy 2.55.3 → 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,6 @@
1
- ## 2.55.3
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.
2
4
 
3
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.
4
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.55.3",
3
+ "version": "2.55.4",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -507,25 +507,67 @@ 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
+
510
558
  /**
511
559
  * How often an actively-read file's subtitle cues are walked ahead of being
512
- * asked for. Cheap once caught up (`warmSubtitleCues` skips clusters it has
513
- * already read), so this can run often; every read.js pass is one it does not
514
- * have to do.
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).
515
564
  */
516
565
  const SUBTITLE_WARMUP_INTERVAL_MS = 3_000;
517
566
 
518
567
  setInterval(() => {
519
568
  for (const [sourceKey, torrent] of pool.torrents) {
520
- const usage = pool.fileUsageByTorrent.get(torrent);
521
- if (!usage) {
522
- continue;
523
- }
524
- for (const fileIndex of usage.keys()) {
525
- warmSubtitleCues(torrent, fileIndex, sourceKey).catch((error) => {
526
- log(`subtitle warmup ${sourceKey}:${fileIndex} failed: ${error instanceof Error ? error.message : error}`);
527
- });
528
- }
569
+ ensureSubtitleWarmupWired(sourceKey, torrent);
570
+ warmActiveFiles(sourceKey, torrent);
529
571
  }
530
572
  }, SUBTITLE_WARMUP_INTERVAL_MS).unref();
531
573