@torrent-tv/proxy 2.105.6 → 2.105.8
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
package/server.js
CHANGED
|
@@ -22,7 +22,7 @@ import { probePackets } from "./services/media/probe-packets.js";
|
|
|
22
22
|
import { trackChoicesOf } from "./services/viewer/segment-demands.js";
|
|
23
23
|
import { PlaybackProjection } from "./services/torrent/PlaybackProjection.js";
|
|
24
24
|
import { SegmentInputTable } from "./services/viewer/segment-input-table.js";
|
|
25
|
-
import { SourcePreparation } from "./services/viewer/SourcePreparation.js";
|
|
25
|
+
import { SourcePreparation, preparationInterval } from "./services/viewer/SourcePreparation.js";
|
|
26
26
|
import { handleHealthzGet } from "./routes/healthz/get.js";
|
|
27
27
|
import { handleApiDeliverySinkGet } from "./routes/api/delivery-sink/get.js";
|
|
28
28
|
import { handleApiLinkProbeGet } from "./routes/api/link-probe/get.js";
|
|
@@ -1403,7 +1403,7 @@ export async function startProxyServer({
|
|
|
1403
1403
|
(work.ownerId === undefined || viewer.id === work.ownerId))
|
|
1404
1404
|
.map(viewer => viewer.positionSeconds() ?? 0);
|
|
1405
1405
|
const from = (positions.length ? Math.min(...positions) : 0) + (Number(info.value?.startTimeSeconds) || 0);
|
|
1406
|
-
params.packetInterval =
|
|
1406
|
+
params.packetInterval = preparationInterval(work.role, from, outputParts.segmentDurationSec);
|
|
1407
1407
|
if (work.role === "subtitle-embedded") {
|
|
1408
1408
|
const tracks = await containerOrchestrator.containerFor(params).then(container => container.readTracks());
|
|
1409
1409
|
const track = tracks.find(track => track.type === "subtitle" && track.declaredIndex === work.trackIndex);
|
|
@@ -76,7 +76,14 @@ export class SubtitleOrchestrator {
|
|
|
76
76
|
endSeconds: packet.duration > 0 ? packet.pts + packet.duration : null, text };
|
|
77
77
|
pending.push({ identity, cue });
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
// A cue read from the file is a fact about the file, not about where
|
|
80
|
+
// the viewer stood when the read began. A position report moves that
|
|
81
|
+
// stand every 1.5 s, so a read that crossed one used to throw away the
|
|
82
|
+
// cues it had read, and those lines never reached the page
|
|
83
|
+
// (torrent-tv/meta#7). They are kept and published; only a forgotten
|
|
84
|
+
// source cannot take them.
|
|
85
|
+
const obsolete = params.isCurrent?.() === false;
|
|
86
|
+
if (this.#packetCues.get(key) !== state) {
|
|
80
87
|
return { kind: "terminal", reason: "request-obsolete", requestId };
|
|
81
88
|
}
|
|
82
89
|
state.ready = true;
|
|
@@ -96,7 +103,7 @@ export class SubtitleOrchestrator {
|
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
105
|
const fresh = TextSubtitleTrack.finalizeCues(ordered(), track.codecId).filter(cue => !previous.has(cue.seq));
|
|
99
|
-
if (fresh.length
|
|
106
|
+
if (fresh.length) {
|
|
100
107
|
const cues = fresh;
|
|
101
108
|
this.cues.publish?.({ sourceKey: params.sourceKey, fileIndex: params.fileIndex,
|
|
102
109
|
trackIndex: track.declaredIndex, cues, withdrawn, language: track.language ?? "",
|
|
@@ -104,6 +111,7 @@ export class SubtitleOrchestrator {
|
|
|
104
111
|
cursor: state.cursor, spanStartSeconds: cues[0]?.startSeconds ?? null,
|
|
105
112
|
spanEndSeconds: cues.at(-1)?.endSeconds ?? null, walkedClusters: 0, indexedClusters: 0 });
|
|
106
113
|
}
|
|
114
|
+
if (obsolete) return { kind: "terminal", reason: "request-obsolete", requestId };
|
|
107
115
|
result = { kind: "result", value: { cues: [...state.packets.values()], track, coveredClusters: 0, indexedClusters: 0 }, requestId };
|
|
108
116
|
}
|
|
109
117
|
} catch (error) {
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many segments ahead of the viewer a chosen embedded subtitle is read.
|
|
3
|
+
*
|
|
4
|
+
* A reading window is taken again only when the viewer is planned again, which
|
|
5
|
+
* is when their report says they entered the next segment, and it starts where
|
|
6
|
+
* that report put them. Reports come every 1.5 s, so a window one segment long
|
|
7
|
+
* ended before the next one began whenever that report came later in its
|
|
8
|
+
* segment than the one before: a cue between the two was never read, and a
|
|
9
|
+
* cue just past a window was read only once the viewer had reached it (field
|
|
10
|
+
* 2026-10-22, Drifters 07: 1 of 27 cues missing, 7 late by 0.2-1.6 s,
|
|
11
|
+
* torrent-tv/meta#7). Two segments let each window reach past the start of the
|
|
12
|
+
* next while the viewer is planned at least once a segment.
|
|
13
|
+
*/
|
|
14
|
+
const SUBTITLE_SEGMENTS_AHEAD = 2;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The stretch of film one preparation read covers from where its viewers stand.
|
|
18
|
+
*
|
|
19
|
+
* @param {string | undefined} role
|
|
20
|
+
* @param {number} from - Seconds on the file's own clock.
|
|
21
|
+
* @param {number} segmentDurationSec
|
|
22
|
+
* @returns {{ from: number, to: number }}
|
|
23
|
+
*/
|
|
24
|
+
export function preparationInterval(role, from, segmentDurationSec) {
|
|
25
|
+
const segments = role === "subtitle-embedded" ? SUBTITLE_SEGMENTS_AHEAD : 1;
|
|
26
|
+
return { from, to: from + segments * segmentDurationSec };
|
|
27
|
+
}
|
|
28
|
+
|
|
1
29
|
/** Source preparation is derived from viewers before any output exists. */
|
|
2
30
|
export class SourcePreparation {
|
|
3
31
|
#viewers;
|