@torrent-tv/proxy 2.107.36 → 2.107.38
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
|
@@ -125,7 +125,10 @@ export class Timeline {
|
|
|
125
125
|
const shift = sourceStartTime - (base ?? 0);
|
|
126
126
|
return {
|
|
127
127
|
from: from === null ? this.publishedStartOf(fromIndex) + sourceStartTime : from + shift,
|
|
128
|
-
to: to === null ? this.publishedStartOf(toIndex + 1) + sourceStartTime : to + shift
|
|
128
|
+
to: to === null ? this.publishedStartOf(toIndex + 1) + sourceStartTime : to + shift,
|
|
129
|
+
// The last piece ends where the source does, wherever its declared
|
|
130
|
+
// duration lands against the frames (`PacketIndex.inputFor`).
|
|
131
|
+
...(toIndex === this.segmentCount - 1 ? { endsWithSource: true } : {})
|
|
129
132
|
};
|
|
130
133
|
}
|
|
131
134
|
|
|
@@ -11,12 +11,13 @@ export class SegmentInputs {
|
|
|
11
11
|
this.#tracks = tracks;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
forInterval({ from, to, tracks = this.#tracks, mode = "transcode" }) {
|
|
14
|
+
forInterval({ from, to, tracks = this.#tracks, mode = "transcode", endsWithSource = false }) {
|
|
15
15
|
const packets = [];
|
|
16
16
|
const ranges = [];
|
|
17
17
|
for (const track of tracks) {
|
|
18
18
|
if (!["audio", "video"].includes(track.type)) continue;
|
|
19
|
-
const input = this.#index.inputFor({ trackId: track.trackNumber, from, to,
|
|
19
|
+
const input = this.#index.inputFor({ trackId: track.trackNumber, from, to,
|
|
20
|
+
mode: typeof mode === "function" ? mode(track) : mode, endsWithSource });
|
|
20
21
|
if (input.kind !== "result") return { ...input, trackId: track.trackNumber };
|
|
21
22
|
packets.push({ track, ...input });
|
|
22
23
|
ranges.push(...input.ranges);
|
|
@@ -175,8 +175,20 @@ export class PacketIndex {
|
|
|
175
175
|
track.coveredThrough = seconds;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
/**
|
|
179
|
-
|
|
178
|
+
/**
|
|
179
|
+
* The same calculation is used to order source bytes and admit an encode.
|
|
180
|
+
*
|
|
181
|
+
* `endsWithSource` says this is the output's last piece: it ends where the
|
|
182
|
+
* source ends, and a copy of it needs no keyframe at `to`. Its `to` is the
|
|
183
|
+
* file's declared duration, and the track's end, its last frame's start plus
|
|
184
|
+
* that frame's duration added in floating point, can land past it: 314.96 s
|
|
185
|
+
* declared, 314.88 + 0.04 = 314.96000000000004 indexed. Compared with that
|
|
186
|
+
* end, the last piece of a copied MP4 was refused, the viewer got HTTP 500 at
|
|
187
|
+
* the end of the film and reopening near the end was refused at once (Home
|
|
188
|
+
* Assistant 2026-10-26, `2026-08-03 QUICK RELIEF 0364 BY KARA_C.mp4`,
|
|
189
|
+
* torrent-tv/meta#239).
|
|
190
|
+
*/
|
|
191
|
+
inputFor({ trackId, from, to, mode = "transcode", endsWithSource = false }) {
|
|
180
192
|
this.flushPending();
|
|
181
193
|
if (!["copy", "transcode"].includes(mode)) throw new TypeError("Unknown segment input mode.");
|
|
182
194
|
if (!Number.isFinite(from) || !Number.isFinite(to) || from < 0 || to <= from) {
|
|
@@ -223,9 +235,13 @@ export class PacketIndex {
|
|
|
223
235
|
: { kind: "needs-index", trackId, from: startAt, to };
|
|
224
236
|
let endsAtKeyframe = false;
|
|
225
237
|
if (mode === "copy") endsAtKeyframe = packets.someAtTime(to, index => packets.keyframeAt(index));
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
const reachesEnd = track.complete && (endsWithSource || to >= this.boundsOf(trackId).end);
|
|
239
|
+
if (mode === "copy" && (packets.ptsAt(first) !== from || (!endsAtKeyframe && !reachesEnd))) {
|
|
240
|
+
const bounds = track.complete ? this.boundsOf(trackId) : null;
|
|
241
|
+
return { kind: "terminal", reason: "video-copy-cut-is-not-a-keyframe",
|
|
242
|
+
message: `copy of ${from}..${to}${endsWithSource ? " (the last piece)" : ""} begins at the keyframe of ` +
|
|
243
|
+
`${packets.ptsAt(first)}, ${endsAtKeyframe ? "ends at a keyframe" : "ends at no keyframe"}; ` +
|
|
244
|
+
(bounds ? `the track ends at ${bounds.end}` : "the track is not indexed to its end yet") };
|
|
229
245
|
}
|
|
230
246
|
}
|
|
231
247
|
// A transcoder needs the first audio packet at or after the cut to flush
|
|
@@ -248,7 +264,7 @@ export class PacketIndex {
|
|
|
248
264
|
decodeFrom: packets.ptsAt(first),
|
|
249
265
|
from,
|
|
250
266
|
to,
|
|
251
|
-
...(track.complete && to >= this.boundsOf(trackId).end ? { sourceEndSeconds: this.boundsOf(trackId).end } : {}),
|
|
267
|
+
...(track.complete && (endsWithSource || to >= this.boundsOf(trackId).end) ? { sourceEndSeconds: this.boundsOf(trackId).end } : {}),
|
|
252
268
|
packets: input
|
|
253
269
|
};
|
|
254
270
|
}
|
|
@@ -229,7 +229,18 @@ export class ViewerRequests {
|
|
|
229
229
|
sourceObserved: true, waitElapsedMs });
|
|
230
230
|
const featuresChanged = this.#recordEarlyWaitSnapshot(context, context.features);
|
|
231
231
|
const elapsedMs = waitElapsedSince(context, waitElapsedMs);
|
|
232
|
-
|
|
232
|
+
// The wait ends when the start forecast lets the picture start, and the
|
|
233
|
+
// first required pieces being there is only part of that: a source
|
|
234
|
+
// supplied slower than the film plays is held until the cushion carries
|
|
235
|
+
// it to the end. Counted to the first pieces alone, the estimate reached
|
|
236
|
+
// zero with seven minutes still to wait, and the page had no time to show
|
|
237
|
+
// (Home Assistant 2026-10-26, Despicable Me 3 BDRemux at 0.53x,
|
|
238
|
+
// torrent-tv/meta#239).
|
|
239
|
+
const firstRequiredSeconds = processing.firstRequiredSegmentsSeconds;
|
|
240
|
+
const startGateSeconds = processing.startGateSeconds;
|
|
241
|
+
const measuredWorkSeconds = Number.isFinite(startGateSeconds) && startGateSeconds >= 0
|
|
242
|
+
? Math.max(startGateSeconds, Number.isFinite(firstRequiredSeconds) ? firstRequiredSeconds : 0)
|
|
243
|
+
: firstRequiredSeconds;
|
|
233
244
|
const hasActiveEstimate = Number.isFinite(measuredWorkSeconds) && measuredWorkSeconds >= 0;
|
|
234
245
|
const stagesChanged = Array.isArray(processing.firstRequiredStages) &&
|
|
235
246
|
JSON.stringify(processing.firstRequiredStages) !== JSON.stringify(context.activeFirstRequiredStages);
|
|
@@ -258,7 +269,8 @@ export class ViewerRequests {
|
|
|
258
269
|
const seconds = earlyWaitRemainingSeconds(totalSeconds, elapsedMs);
|
|
259
270
|
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
260
271
|
this.#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical,
|
|
261
|
-
firstRequiredSegmentsSeconds: hasActiveEstimate ?
|
|
272
|
+
firstRequiredSegmentsSeconds: hasActiveEstimate ? firstRequiredSeconds ?? null : context.activeFirstRequiredSegmentsSeconds,
|
|
273
|
+
startGateSeconds: hasActiveEstimate && Number.isFinite(startGateSeconds) ? startGateSeconds : null,
|
|
262
274
|
firstRequiredStages: context.activeFirstRequiredStages });
|
|
263
275
|
return { version: 1, waitId, revision: context.revision, remainingSeconds: seconds, ageMs: 0 };
|
|
264
276
|
}
|
|
@@ -269,11 +281,14 @@ export class ViewerRequests {
|
|
|
269
281
|
}
|
|
270
282
|
|
|
271
283
|
#logEarlyForecast(context, { elapsedMs, seconds, totalSeconds, historical, firstRequiredSegmentsSeconds,
|
|
272
|
-
firstRequiredStages }) {
|
|
284
|
+
startGateSeconds = null, firstRequiredStages }) {
|
|
273
285
|
if (context.loggedForecastRevision === context.revision) return;
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
286
|
+
const gated = Number.isFinite(startGateSeconds) &&
|
|
287
|
+
!(Number.isFinite(firstRequiredSegmentsSeconds) && firstRequiredSegmentsSeconds >= startGateSeconds);
|
|
288
|
+
const basis = gated ? "start-gate" : Number.isFinite(firstRequiredSegmentsSeconds) ? "first-required" : "historical-stages";
|
|
289
|
+
const estimate = basis !== "historical-stages"
|
|
290
|
+
? { firstRequiredSegmentsSeconds, ...(Number.isFinite(startGateSeconds) ? { startGateSeconds } : {}),
|
|
291
|
+
...(Array.isArray(firstRequiredStages) ? { firstRequiredStages } : {}) }
|
|
277
292
|
: { stagesMs: historical?.stagesMs ?? null, observations: historical?.observations ?? null };
|
|
278
293
|
logger.info(`early wait forecast ${JSON.stringify({ waitId: context.waitId, consumer: context.consumerId,
|
|
279
294
|
generation: context.generation, features: context.features, elapsedMs, revision: context.revision,
|
|
@@ -1121,6 +1136,10 @@ export class ViewerRequests {
|
|
|
1121
1136
|
videoSpeedX: videoOperation?.speed,
|
|
1122
1137
|
audioSpeedX: audioOperation?.speed,
|
|
1123
1138
|
firstRequiredSegmentsSeconds: forecast.firstRequiredSegmentsSeconds,
|
|
1139
|
+
// When the start forecast lets the picture start: its delay while
|
|
1140
|
+
// it holds the start, and nothing to add once it does not.
|
|
1141
|
+
...(forecast.ready !== true && Number.isFinite(forecast.delaySeconds)
|
|
1142
|
+
? { startGateSeconds: forecast.delaySeconds } : {}),
|
|
1124
1143
|
firstRequiredStages: forecast.firstRequiredStages
|
|
1125
1144
|
} });
|
|
1126
1145
|
if (waitEstimate) forecast.waitEstimate = waitEstimate;
|