@torrent-tv/proxy 2.9.124 → 2.9.126

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.9.124",
3
+ "version": "2.9.126",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -19,7 +19,7 @@
19
19
  "major": "npm whoami && npm version major && npm publish && git push --follow-tags",
20
20
  "start": "node ./bin/cli.js",
21
21
  "dev": "node --inspect=0 --experimental-network-inspection ./bin/cli.js",
22
- "test": "node --test",
22
+ "test": "npm run lint && node --test",
23
23
  "lint": "biome lint ."
24
24
  },
25
25
  "dependencies": {
@@ -293,6 +293,12 @@ const PROGRESS_LOG_INTERVAL_MS = 5_000;
293
293
  // hashing starves the event loop in bursts, so fewer read iterations means
294
294
  // far less time lost between chunks while serving the first segments.
295
295
  const SEGMENT_READ_HIGH_WATER_MARK = 4 * 1024 * 1024;
296
+ // How far a segment's own recorded start may sit from the one the playlist
297
+ // assigned it before the disagreement is worth a log line. The two are built
298
+ // from the same keyframe index and normally match to the sample; a quarter of a
299
+ // second is below any drift a viewer could notice, so anything above it is the
300
+ // index being wrong about where a keyframe is rather than rounding.
301
+ const SEGMENT_START_DISAGREEMENT_SEC = 0.25;
296
302
 
297
303
  /**
298
304
  * Resolve after a given number of milliseconds.
@@ -3338,10 +3344,20 @@ export class HlsSessionManager {
3338
3344
  contentType: session.segmentFormat.initContentType,
3339
3345
  isPlaylist: false
3340
3346
  };
3341
- } catch {
3342
- // Not produced yet — the encode run started at session creation writes
3343
- // it early; the caller long-polls until it appears.
3344
- return { kind: "warming-up" };
3347
+ } catch (error) {
3348
+ if (error?.code === "ENOENT") {
3349
+ // Not produced yet — the encode run started at session creation
3350
+ // writes it early; the caller long-polls until it appears.
3351
+ return { kind: "warming-up" };
3352
+ }
3353
+ logger.error(
3354
+ `transcode ${session.id} could not serve ${initFileName}: ${error?.message ?? error}` +
3355
+ (error?.stack ? `\n${error.stack}` : "")
3356
+ );
3357
+ return {
3358
+ kind: "failed",
3359
+ message: `Could not serve ${initFileName}: ${error?.message ?? String(error)}`
3360
+ };
3345
3361
  }
3346
3362
  }
3347
3363
 
@@ -3360,9 +3376,21 @@ export class HlsSessionManager {
3360
3376
  this.#enforceLookAheadFor(session);
3361
3377
  }
3362
3378
  }
3379
+ // Whether the file is there is asked on its own, and nothing else shares
3380
+ // this catch. Everything below is PREPARATION of a file that exists, and a
3381
+ // failure there means something entirely different from "not produced yet"
3382
+ // — but for one release the two were caught together, so an undeclared name
3383
+ // in the fMP4 path read as "the segment is not ready". Every poll threw the
3384
+ // same ReferenceError, every poll answered "wait", and playback never began
3385
+ // on any file cut at keyframes (2.9.124; measured 2026-08-08: segment #0
3386
+ // held for 45 281 ms with twelve finished segments on disk).
3363
3387
  try {
3364
3388
  await access(filePath);
3365
-
3389
+ } catch {
3390
+ // Not produced yet.
3391
+ return this.#holdForProduction(session, fileName, isPlaylist, options);
3392
+ }
3393
+ try {
3366
3394
  // Existing is not the same as finished. The `hls` muxer wrote each
3367
3395
  // segment to a temporary name and renamed it once complete, so a file
3368
3396
  // appearing WAS a finished segment. The `segment` muxer has no such
@@ -3500,10 +3528,38 @@ export class HlsSessionManager {
3500
3528
  : session.segmentFormat.segmentContentType,
3501
3529
  isPlaylist
3502
3530
  };
3503
- } catch (_error) {
3504
- // File not produced yet.
3531
+ } catch (error) {
3532
+ if (error?.code === "ENOENT") {
3533
+ // The file went away between the check and the read — a leftover being
3534
+ // removed so it can be produced again. Means exactly what never having
3535
+ // existed means.
3536
+ return this.#holdForProduction(session, fileName, isPlaylist, options);
3537
+ }
3538
+ // Anything else is a fault in producing the answer. Name it: a request
3539
+ // answered "wait" for ever tells the viewer nothing and leaves no trace
3540
+ // of what actually happened.
3541
+ logger.error(
3542
+ `transcode ${session.id} could not serve ${fileName}: ${error?.message ?? error}` +
3543
+ (error?.stack ? `\n${error.stack}` : "")
3544
+ );
3545
+ return {
3546
+ kind: "failed",
3547
+ message: `Could not serve ${fileName}: ${error?.message ?? String(error)}`
3548
+ };
3505
3549
  }
3550
+ }
3506
3551
 
3552
+ /**
3553
+ * Answer a request for a file that is not on disk: make sure the encoder is
3554
+ * heading for it, and hold the request.
3555
+ *
3556
+ * @param {HlsSession} session
3557
+ * @param {string} fileName
3558
+ * @param {boolean} isPlaylist
3559
+ * @param {{ requestSeq?: number }} options
3560
+ * @returns {{ kind: "warming-up" }}
3561
+ */
3562
+ #holdForProduction(session, fileName, isPlaylist, options) {
3507
3563
  // A segment was requested that ffmpeg has not produced yet. Decide whether
3508
3564
  // to wait for the current encode run to reach it or to restart the encoder
3509
3565
  // at this position (server-side seeking). The caller long-polls.
@@ -10,7 +10,12 @@
10
10
  * See {@link SegmentFormat} in `./index.js` for the interface contract.
11
11
  */
12
12
 
13
- import { readTrackTimescales, stampSegmentStartTime, walkBoxes } from "./mp4-boxes.js";
13
+ import {
14
+ readSelfContainedStartSeconds,
15
+ readTrackTimescales,
16
+ stampSegmentStartTime,
17
+ walkBoxes
18
+ } from "./mp4-boxes.js";
14
19
 
15
20
  /**
16
21
  * How many distinct tracks have a fragment in this segment.