@torrent-tv/proxy 2.9.52 → 2.9.53

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,3 +1,7 @@
1
+ ## 2.9.53
2
+
3
+ - **Fix**: On the video RE-ENCODE path, `processedSeconds` silently switched reference frame partway through every encode run — absolute (matching `startPositionSeconds`) for the placeholder set at restart, then RELATIVE-to-the-run (counting from ~0) the moment ffmpeg's own `-progress out_time`/`out_time_ms` started overwriting it — because `-output_ts_offset` (used to relabel the MUXED output's timestamps onto the absolute grid) does NOT affect what `-progress` reports; verified empirically (a 5s clip encoded with `-output_ts_offset 100` still reports `out_time` counting 0→5, not 100→105). Every consumer of `session.progress.processedSeconds` assumes it is absolute: `computeProgressMetrics` (percent/remaining), `#applyBudgetDownshift`'s mid-run restart point, and — the field-diagnosed symptom — `#ensureEncodingFor`'s look-ahead window, which anchors on `Math.max(head, segmentIndexForTime(processed))`; with `processed` wrongly near-zero this floor pins the window's advancing edge at the run's OWN start segment for its entire lifetime instead of tracking real progress, so any segment request more than `MAX_LOOKAHEAD_SEGMENTS` (8, ≈32s) past the SEEK TARGET reads as "far" and triggers ANOTHER restart — even while the encoder is happily producing well past that point. Field example (verified with a pure-math replay of the exact logged values): seek to 1824s, window pinned at segments 456–464 for the whole run regardless of real progress reaching segment 465+ within seconds, at 6x realtime. This is the mechanism behind "buffering pill stuck at 0% until playback finally starts" and very likely a contributor to the broader "seek gets stuck" class of reports this cycle. Fixed by rebasing `out_time`/`out_time_ms` onto the absolute timeline (`+ session.progress.startPositionSeconds`) for the re-encode branch only — the copy branch already reports absolute time via `-copyts`, unaffected. Verified: a standalone replay of the field's `processed`/`startPos` sequence through the actual `#segmentIndexForTime` algorithm shows the window frozen at the run's start before the fix, correctly advancing with real progress after.
4
+
1
5
  ## 2.9.52
2
6
 
3
7
  - **Chore**: `npm audit` fixes. `@fastify/static` 9.1.3 → 10.1.2 (fixes GHSA-83w8-p2f5-377r route-guard path-traversal bypass and GHSA-8pvw-jcv7-9cmj non-canonical-path authorization bypass — no API change to our usage, verified with a live smoke test: healthz, tunnel connect, and static registration all still work). `brace-expansion`/`fast-uri`/`find-my-way` bumped via `npm audit fix` (transitive, no direct dependency change). Residual: `ip` (via `webtorrent@2.8.5` → `torrent-discovery` → `bittorrent-tracker`) stays flagged high (GHSA-2p57-rm9w-gvfp / CVE-2024-29415, SSRF via `isPublic()` misclassification) — investigated and left as an accepted risk, not an oversight: the advisory has no upstream fix (`first_patched_version: null`, every published version of `ip` is flagged) and `npm audit fix --force`'s only offered fix is downgrading `webtorrent` to 0.7.3, which would reintroduce the exact download-freeze regressions 2.9.44 rolled back from 3.x to avoid. The only actual call site in our dependency tree (`bittorrent-tracker/lib/server/parse-udp.js`) uses `ip.toString()` for UDP-integer→string formatting in the tracker-SERVER's request parser — code we never execute (WebTorrent only uses `bittorrent-tracker` as a tracker CLIENT) — and the vulnerable function itself, `isPublic()`, is not called anywhere in the chain. Revisit if/when a maintained `ip` replacement lands upstream in `bittorrent-tracker`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.9.52",
3
+ "version": "2.9.53",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -1720,6 +1720,42 @@ export class HlsSessionManager {
1720
1720
  this.#wireEncodeProcess(session, ffmpeg);
1721
1721
  }
1722
1722
 
1723
+ /**
1724
+ * Rebase ffmpeg's `-progress` `out_time`/`out_time_ms` onto the SOURCE
1725
+ * (absolute) timeline, so `session.progress.processedSeconds` is always
1726
+ * comparable to `session.progress.startPositionSeconds` — which
1727
+ * `computeProgressMetrics` and the client's own cushion-percent/ETA math
1728
+ * both assume.
1729
+ *
1730
+ * Branch B (video copy, `-copyts`) already reports `out_time` on the
1731
+ * source's absolute timeline — no rebase needed. Branch A (video re-encode)
1732
+ * does NOT: `-output_ts_offset` (used there to relabel the MUXED output's
1733
+ * timestamps onto the absolute grid) does not affect what `-progress`
1734
+ * reports — verified empirically (a 5s clip encoded with
1735
+ * `-output_ts_offset 100` still reports `out_time` counting 0→5, not
1736
+ * 100→105). Left unrebased, `processedSeconds` jumps from the post-restart
1737
+ * placeholder (`session.progress.startPositionSeconds`, absolute) down to a
1738
+ * near-zero RELATIVE value the moment real ffmpeg progress starts flowing —
1739
+ * `processedSeconds - startPositionSeconds` then goes deeply negative,
1740
+ * clamps to 0, and the client's cushion percent/ETA reads as permanently
1741
+ * stuck at 0% for the whole run even while the encode is actively
1742
+ * producing (field-diagnosed 2026-08-01: a re-encode session logged
1743
+ * `processed=39.5 startPos=1824` at a healthy 6x realtime speed).
1744
+ *
1745
+ * @param {HlsSession} session
1746
+ * @param {number} rawSeconds - As parsed from `out_time`/`out_time_ms`.
1747
+ * @returns {number}
1748
+ */
1749
+ #toAbsoluteProcessedSeconds(session, rawSeconds) {
1750
+ if (!session.transcodeVideo) {
1751
+ return rawSeconds;
1752
+ }
1753
+ const offset = Number.isFinite(session.progress?.startPositionSeconds)
1754
+ ? session.progress.startPositionSeconds
1755
+ : 0;
1756
+ return rawSeconds + offset;
1757
+ }
1758
+
1723
1759
  /**
1724
1760
  * Wire stdout (progress), stderr (errors) and exit handlers for an ffmpeg
1725
1761
  * encode process. Handlers no-op when the process has been superseded by a
@@ -1747,12 +1783,12 @@ export class HlsSessionManager {
1747
1783
  if (key === "out_time_ms") {
1748
1784
  const numeric = Number(value);
1749
1785
  if (Number.isFinite(numeric) && numeric >= 0) {
1750
- session.progress.processedSeconds = numeric / MICROSECONDS_PER_SECOND;
1786
+ session.progress.processedSeconds = this.#toAbsoluteProcessedSeconds(session, numeric / MICROSECONDS_PER_SECOND);
1751
1787
  }
1752
1788
  } else if (key === "out_time") {
1753
1789
  const parsed = parseFfmpegTimestamp(value);
1754
1790
  if (parsed != null) {
1755
- session.progress.processedSeconds = parsed;
1791
+ session.progress.processedSeconds = this.#toAbsoluteProcessedSeconds(session, parsed);
1756
1792
  }
1757
1793
  } else if (key === "speed") {
1758
1794
  session.progress.speed = value;