@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/CHANGELOG.md +610 -599
- package/package.json +2 -2
- package/services/hls-session-manager.js +63 -7
- package/services/segment-formats/fmp4.js +6 -1
- package/services/torrent-worker/client.js +468 -449
- package/test/segment-serve-wiring.test.js +205 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@torrent-tv/proxy",
|
|
3
|
-
"version": "2.9.
|
|
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
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
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 (
|
|
3504
|
-
|
|
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 {
|
|
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.
|