@torrent-tv/proxy 2.9.61 → 2.9.62
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 +314 -310
- package/package.json +1 -1
- package/services/hls-session-manager.js +73 -14
package/package.json
CHANGED
|
@@ -1953,20 +1953,26 @@ export class HlsSessionManager {
|
|
|
1953
1953
|
if (index === session.seekFailureTarget && session.seekFailureCount >= MAX_SEEK_FAILURES) {
|
|
1954
1954
|
return;
|
|
1955
1955
|
}
|
|
1956
|
-
//
|
|
1957
|
-
//
|
|
1958
|
-
//
|
|
1959
|
-
//
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1956
|
+
// A far request is NOT treated as a seek. Measured 2026-08-02: on a single
|
|
1957
|
+
// viewer seek the player opens ~25 CONCURRENT requests spanning #904..#1101
|
|
1958
|
+
// and holds them all for the full 60 s without aborting any — normal
|
|
1959
|
+
// read-ahead, not probing. There is therefore no such thing as "the segment
|
|
1960
|
+
// the player ended on": at any instant a couple of dozen different indices
|
|
1961
|
+
// are outstanding, so any rule picking one of them picks noise. Doing so
|
|
1962
|
+
// produced NINE encoder restarts in one minute (#576→#885→#609→#591→#673→
|
|
1963
|
+
// #833→#624→#1071→#1101), each killed 5-8 s in, turning a seek into a
|
|
1964
|
+
// ~70 s ordeal.
|
|
1965
|
+
//
|
|
1966
|
+
// The seek target now arrives explicitly from the browser (requestSeek,
|
|
1967
|
+
// POST /api/transcode-sessions/:id/seek) — the only place the viewer's
|
|
1968
|
+
// intent actually exists. Same split as Jellyfin (startTimeTicks) and
|
|
1969
|
+
// webtor (?t=): requests fetch data, they do not steer the encoder.
|
|
1970
|
+
//
|
|
1971
|
+
// Requests are still valuable, just not as commands: they are a queue of
|
|
1972
|
+
// claims. Held open until produced (the player waits), served from disk
|
|
1973
|
+
// when behind the encoder, and the LOWEST outstanding index marks where the
|
|
1974
|
+
// viewer is actually stalled — the honest input for what to produce first.
|
|
1975
|
+
// See research/hls-seek-prior-art-2026-08-02.md.
|
|
1970
1976
|
}
|
|
1971
1977
|
|
|
1972
1978
|
/**
|
|
@@ -1997,6 +2003,59 @@ export class HlsSessionManager {
|
|
|
1997
2003
|
return Math.max(0, processed - startPosition);
|
|
1998
2004
|
}
|
|
1999
2005
|
|
|
2006
|
+
/**
|
|
2007
|
+
* The viewer seeked. Called from POST /api/transcode-sessions/:id/seek with
|
|
2008
|
+
* the position the browser read off its own player once the scrub ended.
|
|
2009
|
+
*
|
|
2010
|
+
* This is the ONLY thing that repositions the encoder. It replaces inferring
|
|
2011
|
+
* the target from segment requests, which cannot work: a single seek leaves
|
|
2012
|
+
* ~25 concurrent requests outstanding across a wide span (measured), so no
|
|
2013
|
+
* rule over them can recover which one the viewer meant.
|
|
2014
|
+
*
|
|
2015
|
+
* The existing settle/cooldown/first-segment guards still apply — they
|
|
2016
|
+
* protect against restarting too eagerly, which is orthogonal to knowing
|
|
2017
|
+
* WHERE to restart.
|
|
2018
|
+
*
|
|
2019
|
+
* @param {string} sessionId
|
|
2020
|
+
* @param {number} positionSeconds - Absolute position on the source timeline.
|
|
2021
|
+
* @returns {boolean} False when the session is unknown or disposed.
|
|
2022
|
+
*/
|
|
2023
|
+
requestSeek(sessionId, positionSeconds) {
|
|
2024
|
+
const session = this.sessionsById.get(sessionId);
|
|
2025
|
+
if (!session || session.state === "disposed") {
|
|
2026
|
+
return false;
|
|
2027
|
+
}
|
|
2028
|
+
const index = this.#segmentIndexForTime(session, positionSeconds);
|
|
2029
|
+
const head = session.encodeStartIndex;
|
|
2030
|
+
const processed = Number.isFinite(session.progress?.processedSeconds)
|
|
2031
|
+
? session.progress.processedSeconds
|
|
2032
|
+
: this.#segmentStartTime(session, head);
|
|
2033
|
+
const currentSeg = Math.max(head, this.#segmentIndexForTime(session, processed));
|
|
2034
|
+
// Already covered by the running encode — the data is on its way, so
|
|
2035
|
+
// restarting would only destroy work the viewer is waiting for.
|
|
2036
|
+
if (index >= head && index <= currentSeg + MAX_LOOKAHEAD_SEGMENTS) {
|
|
2037
|
+
logger.info(
|
|
2038
|
+
`transcode ${session.id} seek to ${positionSeconds.toFixed(1)}s (#${index}) ` +
|
|
2039
|
+
`already within the running encode (#${head}..#${currentSeg}) — not restarting`
|
|
2040
|
+
);
|
|
2041
|
+
return true;
|
|
2042
|
+
}
|
|
2043
|
+
logger.info(
|
|
2044
|
+
`transcode ${session.id} viewer seek to ${positionSeconds.toFixed(1)}s → segment #${index}`
|
|
2045
|
+
);
|
|
2046
|
+
session.seekTarget = index;
|
|
2047
|
+
if (session.seekSettleTimer) {
|
|
2048
|
+
clearTimeout(session.seekSettleTimer);
|
|
2049
|
+
} else {
|
|
2050
|
+
session.seekFirstFarAt = Date.now();
|
|
2051
|
+
}
|
|
2052
|
+
const waited = Date.now() - session.seekFirstFarAt;
|
|
2053
|
+
const delay = waited >= SEEK_SETTLE_MAX_MS ? 0 : Math.min(SEEK_SETTLE_MS, SEEK_SETTLE_MAX_MS - waited);
|
|
2054
|
+
session.seekSettleTimer = setTimeout(() => this.#fireSettledSeek(session), delay);
|
|
2055
|
+
session.seekSettleTimer.unref?.();
|
|
2056
|
+
return true;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2000
2059
|
#fireSettledSeek(session) {
|
|
2001
2060
|
const target = session.seekTarget;
|
|
2002
2061
|
session.seekSettleTimer = null;
|