@torrent-tv/proxy 2.15.1 → 2.15.2
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.15.2
|
|
2
|
+
|
|
3
|
+
- **Fix**: A segment request that can never be answered is answered as absent instead of being held for a minute. Changing audio track makes hls.js ask the new stream for segment #0 before anything else; the run was at #354, the repair reaches sixty segments back and no further, no seek was coming, and an encoder only moves forward — so the request was unanswerable from the moment it arrived, and holding it simply spent the player's own patience. Measured 2026-08-15: the track was made ready in 7.1 s at the viewer's position, and the viewer then watched a spinner for **63 s** — sixty of them the hold, the rest the player recovering after it failed. Deliberately narrower than the refusal 2.14.1 shipped and 2.14.2 withdrew: a request within the repair's reach, or one with a seek on its way, is still held, because for those the encoder is about to be moved there.
|
|
4
|
+
|
|
1
5
|
## 2.15.1
|
|
2
6
|
|
|
3
7
|
- **Fix**: A magnet that never found its metadata no longer makes the same film unplayable from its own `.torrent`. One infohash is one torrent, so the second add is refused and the pool takes the one already there — which is right when it is ready and wrong when it is not: a magnet whose swarm has not answered has no file list, and everything bound to it is answered 404. Measured 2026-08-15 on the addon host: a magnet with no reachable trackers was added first, the film's own `.torrent` then joined that empty torrent instead of replacing it, `/stream` answered 404, the encoder died on its first read, and the film stayed unplayable until the proxy was restarted. A `.torrent` carries the file list, the piece hashes and the trackers outright, so when it meets a torrent with no metadata it now replaces it; two magnets still wait, because neither has anything the other lacks.
|
package/package.json
CHANGED
|
@@ -6067,6 +6067,35 @@ export class HlsSessionManager {
|
|
|
6067
6067
|
// at this position (server-side seeking). The caller long-polls.
|
|
6068
6068
|
if (!isPlaylist) {
|
|
6069
6069
|
const requestedIndex = session.segmentFormat.segmentIndexFromName(fileName);
|
|
6070
|
+
// Unanswerable, and known to be: behind a run that only moves forward,
|
|
6071
|
+
// too far behind for the repair to fetch it, and no seek on its way to
|
|
6072
|
+
// move the encoder there. Holding it changes nothing about whether it can
|
|
6073
|
+
// be produced — it only spends the player's patience.
|
|
6074
|
+
//
|
|
6075
|
+
// This is what a track change costs when it is held instead: measured
|
|
6076
|
+
// 2026-08-15, hls.js asked the new track for segment #0 while the run was
|
|
6077
|
+
// at #354, the request was held for the full minute, and only when it
|
|
6078
|
+
// failed did the player move to the segment it actually needed — 63 s of
|
|
6079
|
+
// spinner after a track that had been made ready in 7.
|
|
6080
|
+
//
|
|
6081
|
+
// Narrow on purpose. A request behind the head is USUALLY temporary: the
|
|
6082
|
+
// repair moves the encoder back for anything within its reach, and a
|
|
6083
|
+
// reported seek is about to move it anyway. Refusing those was 2.14.1,
|
|
6084
|
+
// and it left a viewer retrying a 404 for ever.
|
|
6085
|
+
if (
|
|
6086
|
+
Number.isFinite(requestedIndex) &&
|
|
6087
|
+
requestedIndex < (session.encodeStartIndex ?? 0) &&
|
|
6088
|
+
(session.encodeStartIndex ?? 0) - requestedIndex > BEHIND_HEAD_REPAIR_MAX_SEGMENTS &&
|
|
6089
|
+
session.ffmpeg != null &&
|
|
6090
|
+
session.seekTarget == null &&
|
|
6091
|
+
session.seekSettleTimer == null
|
|
6092
|
+
) {
|
|
6093
|
+
logger.info(
|
|
6094
|
+
`transcode ${session.id} segment #${requestedIndex} is ${(session.encodeStartIndex ?? 0) - requestedIndex} ` +
|
|
6095
|
+
`segments behind the run and beyond the repair's reach; answered as absent rather than held`
|
|
6096
|
+
);
|
|
6097
|
+
return { kind: "not-found" };
|
|
6098
|
+
}
|
|
6070
6099
|
this.#ensureEncodingFor(
|
|
6071
6100
|
session,
|
|
6072
6101
|
requestedIndex,
|
|
@@ -108,12 +108,11 @@ test("a burst of different segments behind the run is a scan, and moves nothing"
|
|
|
108
108
|
for (const index of scanned) {
|
|
109
109
|
session.firstWantedAt.set(index, Date.now() - 1000);
|
|
110
110
|
}
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
111
|
+
// Asked ONCE each, which is what a scan is: the player opens them together
|
|
112
|
+
// and abandons them together. Field log 2026-08-02 — #178, #681, #725, #807,
|
|
113
|
+
// #74, #245, #387 within half a second, none of them repeated.
|
|
114
|
+
for (const index of scanned) {
|
|
115
|
+
await manager.getFileStream(SESSION_ID, fmp4Format.segmentFileName(index), { requestSeq: 1 });
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
assert.equal(
|
|
@@ -219,3 +218,34 @@ test("a request ahead of the run is not touched", async (t) => {
|
|
|
219
218
|
"the running encode may yet reach it; restarting on a far request is what produced nine restarts in a minute"
|
|
220
219
|
);
|
|
221
220
|
});
|
|
221
|
+
|
|
222
|
+
test("a request far beyond the repair's reach is answered at once, not held", async (t) => {
|
|
223
|
+
const { manager, session, dirPath } = await managerWithRunAhead();
|
|
224
|
+
t.after(async () => {
|
|
225
|
+
await manager.disposeAll();
|
|
226
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
227
|
+
});
|
|
228
|
+
// What a track change does: hls.js asks the new stream for segment #0 while
|
|
229
|
+
// the run is hundreds of segments in. The repair cannot reach it, no seek is
|
|
230
|
+
// coming, and the run only moves forward — so it can never be produced.
|
|
231
|
+
// Measured 2026-08-15: held for the full minute, and the viewer watched a
|
|
232
|
+
// spinner for 63 s after a track that had been made ready in 7.
|
|
233
|
+
const answer = await manager.getFileStream(SESSION_ID, fmp4Format.segmentFileName(0), { requestSeq: 1 });
|
|
234
|
+
|
|
235
|
+
assert.equal(answer.kind, "not-found", "answered, so the player can move on to what it can have");
|
|
236
|
+
assert.equal(session.seekTarget, null, "and the encoder was not sent to the start of the film for it");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("a request just behind the run is still held, because the repair will fetch it", async (t) => {
|
|
240
|
+
const { manager, session, dirPath } = await managerWithRunAhead();
|
|
241
|
+
t.after(async () => {
|
|
242
|
+
await manager.disposeAll();
|
|
243
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
244
|
+
});
|
|
245
|
+
const name = fmp4Format.segmentFileName(WANTED);
|
|
246
|
+
session.firstWantedAt.set(WANTED, Date.now() - 1000);
|
|
247
|
+
|
|
248
|
+
const answer = await manager.getFileStream(SESSION_ID, name, { requestSeq: 1 });
|
|
249
|
+
|
|
250
|
+
assert.equal(answer.kind, "warming-up", "within reach: the encoder is about to be moved there");
|
|
251
|
+
});
|