@torrent-tv/proxy 2.9.101 → 2.9.102
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 +4 -0
- package/package.json +1 -1
- package/server.js +2 -1
- package/services/hls-session-manager.js +35 -0
- package/services/playback-planner.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.9.102
|
|
2
|
+
|
|
3
|
+
- **New**: The playback plan also reports what this host takes to CREATE a session — median of the last eight, 116-843 ms depending on whether the keyframe index is already in hand. It is the second term of the browser's end-to-end estimate, which is being rebuilt as a sum over the stages that have not happened yet rather than a choice between figures that each describe only one of them (`research/playback-eta-2026-08-05.md`).
|
|
4
|
+
|
|
1
5
|
## 2.9.101
|
|
2
6
|
|
|
3
7
|
- **New**: The playback plan reports what this host takes to produce a session's first segment — the median of its last eight, measured from session-create to a servable segment (782-1518 ms on the field host). The browser needs it for the gap between "the file is downloaded" and "a segment exists", where until now it assumed the pipeline merely keeps up with realtime and therefore showed 15 s where 3.8 s were left. It is per-host, so a weak box and a fast one each answer for themselves.
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -165,7 +165,8 @@ export async function startProxyServer({ host, port, transcodeAudio, ffmpegBin,
|
|
|
165
165
|
sourceRegistry,
|
|
166
166
|
torrentPool,
|
|
167
167
|
warmKeyframeIndex: (params) => hlsSessionManager.warmKeyframeIndex(params),
|
|
168
|
-
expectedFirstSegmentMs: () => hlsSessionManager.expectedFirstSegmentMs()
|
|
168
|
+
expectedFirstSegmentMs: () => hlsSessionManager.expectedFirstSegmentMs(),
|
|
169
|
+
expectedSessionCreateMs: () => hlsSessionManager.expectedSessionCreateMs()
|
|
169
170
|
});
|
|
170
171
|
|
|
171
172
|
app.get("/health", async (req, reply) => handleHealthGet(req, reply, { version }));
|
|
@@ -808,6 +808,16 @@ export class HlsSessionManager {
|
|
|
808
808
|
*/
|
|
809
809
|
#firstSegmentLatencies = [];
|
|
810
810
|
|
|
811
|
+
/**
|
|
812
|
+
* Recent times to create a session, in ms — the second term of the browser's
|
|
813
|
+
* estimate. Measured for the same reason as the first: it is 116-843 ms
|
|
814
|
+
* depending on whether the keyframe index is already in hand, and guessing it
|
|
815
|
+
* was one of the ways the shown figure stopped describing the whole wait.
|
|
816
|
+
*
|
|
817
|
+
* @type {number[]}
|
|
818
|
+
*/
|
|
819
|
+
#sessionCreateLatencies = [];
|
|
820
|
+
|
|
811
821
|
/**
|
|
812
822
|
* @param {HlsSessionManagerOptions} options
|
|
813
823
|
*/
|
|
@@ -1121,6 +1131,7 @@ export class HlsSessionManager {
|
|
|
1121
1131
|
);
|
|
1122
1132
|
});
|
|
1123
1133
|
}
|
|
1134
|
+
this.#rememberSessionCreateLatency(Date.now() - createEntryMs);
|
|
1124
1135
|
logger.info(
|
|
1125
1136
|
`cold-start ${sessionId.slice(0, 8)}: media-info=${mediaInfoMs}ms (${mediaInfoSource}) ` +
|
|
1126
1137
|
`keyframes=${keyframeMs === -1 ? "skipped" : keyframeMs === -2 ? "background" : `${keyframeMs}ms`} ` +
|
|
@@ -2750,6 +2761,30 @@ export class HlsSessionManager {
|
|
|
2750
2761
|
* @param {number} latencyMs
|
|
2751
2762
|
* @returns {void}
|
|
2752
2763
|
*/
|
|
2764
|
+
#rememberSessionCreateLatency(latencyMs) {
|
|
2765
|
+
if (!Number.isFinite(latencyMs) || latencyMs <= 0) {
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
this.#sessionCreateLatencies.push(latencyMs);
|
|
2769
|
+
if (this.#sessionCreateLatencies.length > FIRST_SEGMENT_SAMPLES) {
|
|
2770
|
+
this.#sessionCreateLatencies.shift();
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
/**
|
|
2775
|
+
* What this host typically takes to create a session, in ms — the median of
|
|
2776
|
+
* recent ones, or null before any has finished.
|
|
2777
|
+
*
|
|
2778
|
+
* @returns {number | null}
|
|
2779
|
+
*/
|
|
2780
|
+
expectedSessionCreateMs() {
|
|
2781
|
+
if (this.#sessionCreateLatencies.length === 0) {
|
|
2782
|
+
return null;
|
|
2783
|
+
}
|
|
2784
|
+
const sorted = [...this.#sessionCreateLatencies].sort((left, right) => left - right);
|
|
2785
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2753
2788
|
#rememberFirstSegmentLatency(latencyMs) {
|
|
2754
2789
|
if (!Number.isFinite(latencyMs) || latencyMs <= 0) {
|
|
2755
2790
|
return;
|
|
@@ -269,6 +269,7 @@ export function createPlaybackPlanner({
|
|
|
269
269
|
// downloaded" and "a segment exists": until now it assumed the pipeline
|
|
270
270
|
// merely keeps up with realtime, and showed 15 s where 3.8 s were left.
|
|
271
271
|
expectedFirstSegmentMs,
|
|
272
|
+
expectedSessionCreateMs,
|
|
272
273
|
// Optional. Called once the file's edges are downloaded, so the keyframe
|
|
273
274
|
// index — which reads the same tail of the file — is fetched alongside the
|
|
274
275
|
// codec probe instead of after it. Late-bound to the HLS session manager,
|
|
@@ -402,6 +403,7 @@ export function createPlaybackPlanner({
|
|
|
402
403
|
const { audioCodec, videoCodec, container, durationSeconds, videoWidth, videoHeight, audioTracks, subtitleTracks } = probe;
|
|
403
404
|
const codecsDetected = audioCodec.length > 0 || videoCodec.length > 0;
|
|
404
405
|
const firstSegmentMs = expectedFirstSegmentMs?.() ?? null;
|
|
406
|
+
const sessionCreateMs = expectedSessionCreateMs?.() ?? null;
|
|
405
407
|
logger.info(
|
|
406
408
|
`plan ${sourceKey.slice(0, 8)}:${fileIndex} torrent-ready=${torrentReadyMs}ms ` +
|
|
407
409
|
`file-edges=${edgesReadyMs - torrentReadyMs}ms probe=${Date.now() - planEntryMs - edgesReadyMs}ms ` +
|
|
@@ -430,7 +432,9 @@ export function createPlaybackPlanner({
|
|
|
430
432
|
subtitleTracks: subtitleTracks ?? [],
|
|
431
433
|
// What this host has recently taken to make a session's first segment.
|
|
432
434
|
// Null until one has finished since startup.
|
|
433
|
-
expectedFirstSegmentMs: firstSegmentMs
|
|
435
|
+
expectedFirstSegmentMs: firstSegmentMs,
|
|
436
|
+
// Second term of the browser's estimate; see research/playback-eta-2026-08-05.md.
|
|
437
|
+
expectedSessionCreateMs: sessionCreateMs
|
|
434
438
|
};
|
|
435
439
|
// Only cache a plan whose codecs were actually detected. An empty probe is
|
|
436
440
|
// a "header not downloaded yet" signal, not a valid result — caching it
|