@torrent-tv/proxy 2.9.58 → 2.9.59

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.59
2
+
3
+ - **Chore**: Diagnostics for seek handling. The session-start line now carries the proxy version (`transcode <id> start (proxy 2.9.59) "<file>"`), so a field report answers "is the host running the build I published?" by itself. And the seek restart guard added in 2.9.58 now states its decision: either `seek #N HELD — current run has produced Xs of the Ys first segment` or, on the restart line, why it was allowed (`run is dead` / `run produced Xs (first segment done)` / `grace expired`). Previously a permitted restart was indistinguishable in the log from the runaway ping-pong the guard exists to stop, which made diagnosing "seek still did not work" guesswork.
4
+
1
5
  ## 2.9.58
2
6
 
3
7
  - **Fix**: A single user seek could leave playback permanently stuck with a flickering loading pill and no video. The encoder was allowed to restart at a new position even when the current run had not yet produced a **single segment**, so each restart destroyed the previous one's work and began the wait again — self-perpetuating, because the first segment after a seek is the slowest thing the pipeline does (field log 2026-08-02: restarts at #617 → #717 → #732 → #732 every 5-7 s, none producing anything). The extra targets were not further user seeks: unable to get its segment, the player SCANS the playlist (our synthetic VOD playlist lists every segment, so from its side they all exist), and each far-enough probe looked like a fresh seek. A seek restart now waits for the current run to produce its first segment (bounded by a 30 s grace, and skipped entirely if the run has died), which makes the scan harmless and lets one genuine seek complete. Independent of segment format.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.9.58",
3
+ "version": "2.9.59",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -14,7 +14,11 @@ import os from "node:os";
14
14
  import path from "node:path";
15
15
  import { randomUUID } from "node:crypto";
16
16
  import { spawn } from "node:child_process";
17
+ import { createRequire } from "node:module";
17
18
  import { logger } from "../utils/logger.js";
19
+
20
+ /** Own package version, stamped onto session-start log lines. */
21
+ const PROXY_VERSION = createRequire(import.meta.url)("../package.json").version;
18
22
  import {
19
23
  softwareDescriptor,
20
24
  chooseSoftwareEncodeSettings,
@@ -1077,7 +1081,10 @@ export class HlsSessionManager {
1077
1081
  this.sessionIdBySource.set(sourceMapKey, sessionId);
1078
1082
 
1079
1083
  logger.info(
1080
- `transcode ${sessionId} start "${logName}" ` +
1084
+ // Proxy version on the session-start line: a field report always includes
1085
+ // one of these, so "is the host actually running the build I published?"
1086
+ // is answered by the log itself instead of a round trip to the machine.
1087
+ `transcode ${sessionId} start (proxy ${PROXY_VERSION}) "${logName}" ` +
1081
1088
  `video=${transcodeVideo ? `${this.videoEncoder.name}${softwarePreset ? `/${softwarePreset}` : ""}` : "copy"} ` +
1082
1089
  `audio=${transcodeAudio ? "aac" : "copy"} ` +
1083
1090
  // Branch tag for log correlation: A = video re-encode (fixed GOP, grid
@@ -2040,13 +2047,26 @@ export class HlsSessionManager {
2040
2047
  producedThisRun < this.segmentDurationSec &&
2041
2048
  sinceLastRestart < RUN_FIRST_SEGMENT_GRACE_MS
2042
2049
  ) {
2050
+ logger.info(
2051
+ `transcode ${session.id} seek #${target} HELD — current run has produced ` +
2052
+ `${producedThisRun.toFixed(1)}s of the ${this.segmentDurationSec}s first segment ` +
2053
+ `(${(sinceLastRestart / 1000).toFixed(1)}s into a ${RUN_FIRST_SEGMENT_GRACE_MS / 1000}s grace)`
2054
+ );
2043
2055
  session.seekSettleTimer = setTimeout(() => this.#fireSettledSeek(session), SEEK_SETTLE_MS);
2044
2056
  session.seekSettleTimer.unref?.();
2045
2057
  return;
2046
2058
  }
2059
+ // Why the restart was allowed — the counterpart of the HELD line above.
2060
+ // Without it a restart is indistinguishable from the runaway ping-pong this
2061
+ // guard exists to stop, and diagnosing a field report becomes guesswork.
2062
+ const allowedBecause = !runIsAlive
2063
+ ? "run is dead"
2064
+ : producedThisRun >= this.segmentDurationSec
2065
+ ? `run produced ${producedThisRun.toFixed(1)}s (first segment done)`
2066
+ : `grace of ${RUN_FIRST_SEGMENT_GRACE_MS / 1000}s expired`;
2047
2067
  session.seekTarget = null;
2048
2068
  session.seekFirstFarAt = 0;
2049
- logger.info(`transcode ${session.id} seek settle → restart at segment #${target}`);
2069
+ logger.info(`transcode ${session.id} seek settle → restart at segment #${target} (${allowedBecause})`);
2050
2070
  void this.#startEncodeRun(session, target);
2051
2071
  }
2052
2072