@torrent-tv/proxy 2.107.29 → 2.107.30
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
|
@@ -917,7 +917,7 @@ export async function startProxyServer({
|
|
|
917
917
|
reviseBudget: () => { void outputParts.machineBudget.revise("memory", "media metadata").catch(error => logger.warn(`packet index budget: ${error.message}`)); },
|
|
918
918
|
changed: () => { mediaReads.memoryChanged(); probeReads.memoryChanged(); outputParts.encodeInputs?.memoryChanged(); }
|
|
919
919
|
});
|
|
920
|
-
const containerOver = async ({ sourceKey, fileIndex, requestId, packetInterval, demand }) => {
|
|
920
|
+
const containerOver = async ({ sourceKey, fileIndex, requestId, packetInterval, demand, readPriority }) => {
|
|
921
921
|
const record = sourceRegistry.get(sourceKey);
|
|
922
922
|
if (!record) {
|
|
923
923
|
return null;
|
|
@@ -932,6 +932,7 @@ export async function startProxyServer({
|
|
|
932
932
|
fileIndex,
|
|
933
933
|
requestId,
|
|
934
934
|
packetInterval,
|
|
935
|
+
readPriority,
|
|
935
936
|
packetMemory: indexMemory.forFile(sourceKey, fileIndex),
|
|
936
937
|
downloadInterval: demand ? { from: demand.from, to: demand.to } : null,
|
|
937
938
|
readRange: (start, end) =>
|
|
@@ -1023,7 +1024,7 @@ export async function startProxyServer({
|
|
|
1023
1024
|
},
|
|
1024
1025
|
// `timing`, when given, is told how long each read of a file waited behind
|
|
1025
1026
|
// the reads asked before it and how long it took (torrent-tv/meta#166).
|
|
1026
|
-
resolveEncodeInput: async (output, fromIndex, toIndex, timing = null, isCurrent = null, signal = null) => {
|
|
1027
|
+
resolveEncodeInput: async (output, fromIndex, toIndex, timing = null, isCurrent = null, signal = null, readPriority = null) => {
|
|
1027
1028
|
const grid = output.timeline?.published ?? output.timeline?.boundaries;
|
|
1028
1029
|
const from = grid?.[fromIndex], to = grid?.[toIndex + 1];
|
|
1029
1030
|
if (!Number.isFinite(from) || !(to > from)) return { kind: "terminal", reason: "output-interval-not-declared" };
|
|
@@ -1041,7 +1042,7 @@ export async function startProxyServer({
|
|
|
1041
1042
|
// reassembled packets otherwise.
|
|
1042
1043
|
const files = [];
|
|
1043
1044
|
for (const [fileIndex, choices] of selected) {
|
|
1044
|
-
const params = await containerOver({ sourceKey: output.file.sourceKey, fileIndex });
|
|
1045
|
+
const params = await containerOver({ sourceKey: output.file.sourceKey, fileIndex, readPriority });
|
|
1045
1046
|
if (!params) return { kind: "needs-source" };
|
|
1046
1047
|
if (isCurrent) params.isCurrent = isCurrent;
|
|
1047
1048
|
if (signal) params.signal = signal;
|
|
@@ -100,7 +100,8 @@ export class EncodeInputs {
|
|
|
100
100
|
if (request.noted === state) return;
|
|
101
101
|
request.noted = state;
|
|
102
102
|
const serves = request.serves ?? request.from;
|
|
103
|
-
const rank =
|
|
103
|
+
const rank = request.readPriority?.serves === serves
|
|
104
|
+
? request.readPriority : this.#rankAt(request.output, serves);
|
|
104
105
|
this.#log(`encode input #${request.from}..#${request.to} of ${request.output.outputKey ?? request.output.id}: ${state}` +
|
|
105
106
|
` (held ${this.held()} of ${this.#allowed} allowed; for #${serves}` +
|
|
106
107
|
`${rank ? `, rank ${rank.rank} of ${rank.topRank}` : ""})${detail}`);
|
|
@@ -115,10 +116,10 @@ export class EncodeInputs {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/** The container's statement of an interval, its time added to the request's account. */
|
|
118
|
-
async #resolveTimed(timing, output, from, to, isCurrent = null, signal = null) {
|
|
119
|
+
async #resolveTimed(timing, output, from, to, isCurrent = null, signal = null, readPriority = null) {
|
|
119
120
|
const startedAt = this.#now();
|
|
120
121
|
try {
|
|
121
|
-
return await this.#resolve(output, from, to, timing, isCurrent, signal);
|
|
122
|
+
return await this.#resolve(output, from, to, timing, isCurrent, signal, readPriority);
|
|
122
123
|
} finally {
|
|
123
124
|
timing.resolutions += 1;
|
|
124
125
|
timing.resolveMs += this.#now() - startedAt;
|
|
@@ -409,6 +410,7 @@ export class EncodeInputs {
|
|
|
409
410
|
#prepare(request) {
|
|
410
411
|
request.controller ??= new AbortController();
|
|
411
412
|
request.pending = true;
|
|
413
|
+
request.readPriority = this.#readPriority(request, request.from, request.from);
|
|
412
414
|
request.revision = this.#revision;
|
|
413
415
|
request.memoryRevision = this.#memoryRevision;
|
|
414
416
|
request.arrivedMeanwhile = null;
|
|
@@ -425,7 +427,8 @@ export class EncodeInputs {
|
|
|
425
427
|
// packet walk, left to run, held the file's reads and the piece the
|
|
426
428
|
// viewer had seeked to waited 16.9 s behind it (r59, torrent-tv/meta#178).
|
|
427
429
|
const resolved = await this.#resolveTimed(request.timing, request.output, request.from, request.from,
|
|
428
|
-
() => this.#requests.get(request.key) === request && !request.controller.signal.aborted, request.controller.signal
|
|
430
|
+
() => this.#requests.get(request.key) === request && !request.controller.signal.aborted, request.controller.signal,
|
|
431
|
+
request.readPriority);
|
|
429
432
|
if (resolved.kind === "result") this.#original.set(request.output, resolved.sources?.every(source => source.input.original === true) === true);
|
|
430
433
|
if (this.#requests.get(request.key) !== request) return;
|
|
431
434
|
let result;
|
|
@@ -492,6 +495,23 @@ export class EncodeInputs {
|
|
|
492
495
|
});
|
|
493
496
|
}
|
|
494
497
|
|
|
498
|
+
/**
|
|
499
|
+
* The media reads that prepare one input keep the output map's urgency and
|
|
500
|
+
* the segment they serve, so a container can run an exact nearby interval
|
|
501
|
+
* before a speculative attempt to extend that input.
|
|
502
|
+
*
|
|
503
|
+
* @param {object} request
|
|
504
|
+
* @param {number} from
|
|
505
|
+
* @param {number} to
|
|
506
|
+
* @returns {{ rank: number, serves: number, from: number, to: number }}
|
|
507
|
+
*/
|
|
508
|
+
#readPriority(request, from, to) {
|
|
509
|
+
const serves = request.serves ?? request.from;
|
|
510
|
+
const rank = this.#rankAt(request.output, serves);
|
|
511
|
+
return { rank: Number.isFinite(rank?.rank) ? rank.rank : 0, topRank: Number.isFinite(rank?.topRank) ? rank.topRank : 0,
|
|
512
|
+
serves, from, to };
|
|
513
|
+
}
|
|
514
|
+
|
|
495
515
|
/**
|
|
496
516
|
* Whether bytes came during a try that the try may have missed. A try that
|
|
497
517
|
* waits for named bytes is repeated only when some of them came, or when an
|
|
@@ -563,7 +583,9 @@ export class EncodeInputs {
|
|
|
563
583
|
if (!stretches.has(end)) {
|
|
564
584
|
// A longer stretch that cannot be stated is not one to copy; the
|
|
565
585
|
// shorter one already stated still runs.
|
|
566
|
-
const
|
|
586
|
+
const current = () => this.#requests.get(request.key) === request && !request.controller.signal.aborted;
|
|
587
|
+
const resolved = await this.#resolveTimed(request.timing, output, from, end, current, request.controller.signal,
|
|
588
|
+
this.#readPriority(request, from, end)).catch(() => null);
|
|
567
589
|
stretches.set(end, resolved?.kind === "result" && resolved.sources?.every(source => source.input.original === true)
|
|
568
590
|
? { sources: resolved.sources, bytes: originalInputBytes(resolved.sources) } : null);
|
|
569
591
|
}
|
|
@@ -32,6 +32,28 @@ import { logger } from "../../utils/logger.js";
|
|
|
32
32
|
*/
|
|
33
33
|
export const CONTAINER_HEAD_BYTES = 256 * 1024;
|
|
34
34
|
|
|
35
|
+
/** Higher urgency, earlier served time, then the smaller source interval. */
|
|
36
|
+
function compareReadPriority(left, right) {
|
|
37
|
+
const leftRanked = Number.isFinite(left.readPriority?.rank) && left.readPriority.rank > 0;
|
|
38
|
+
const rightRanked = Number.isFinite(right.readPriority?.rank) && right.readPriority.rank > 0;
|
|
39
|
+
if (leftRanked !== rightRanked) return rightRanked - leftRanked;
|
|
40
|
+
if (!leftRanked) return left.sequence - right.sequence;
|
|
41
|
+
const leftRank = left.readPriority.rank;
|
|
42
|
+
const rightRank = right.readPriority.rank;
|
|
43
|
+
if (leftRank !== rightRank) return rightRank - leftRank;
|
|
44
|
+
const leftServes = Number.isFinite(left.readPriority?.serves) ? left.readPriority.serves : Number.POSITIVE_INFINITY;
|
|
45
|
+
const rightServes = Number.isFinite(right.readPriority?.serves) ? right.readPriority.serves : Number.POSITIVE_INFINITY;
|
|
46
|
+
if (leftServes !== rightServes) return leftServes - rightServes;
|
|
47
|
+
const leftSpan = Number.isFinite(left.readPriority?.from) && Number.isFinite(left.readPriority?.to)
|
|
48
|
+
? Math.max(0, left.readPriority.to - left.readPriority.from) : Number.POSITIVE_INFINITY;
|
|
49
|
+
const rightSpan = Number.isFinite(right.readPriority?.from) && Number.isFinite(right.readPriority?.to)
|
|
50
|
+
? Math.max(0, right.readPriority.to - right.readPriority.from) : Number.POSITIVE_INFINITY;
|
|
51
|
+
if (leftSpan !== rightSpan) return leftSpan - rightSpan;
|
|
52
|
+
const leftFrom = Number.isFinite(left.readPriority?.from) ? left.readPriority.from : Number.POSITIVE_INFINITY;
|
|
53
|
+
const rightFrom = Number.isFinite(right.readPriority?.from) ? right.readPriority.from : Number.POSITIVE_INFINITY;
|
|
54
|
+
return leftFrom - rightFrom || left.sequence - right.sequence;
|
|
55
|
+
}
|
|
56
|
+
|
|
35
57
|
/**
|
|
36
58
|
* The size of the file and its OpenSubtitles hash, from the two edges. Nothing
|
|
37
59
|
* partial is kept: an edge that has not arrived is named instead.
|
|
@@ -101,6 +123,7 @@ export class ContainerOrchestrator {
|
|
|
101
123
|
#lifetimes = new Map();
|
|
102
124
|
#activityEpoch = 0;
|
|
103
125
|
#activeReads = 0;
|
|
126
|
+
#readSequence = 0;
|
|
104
127
|
|
|
105
128
|
activity() { return { epoch: this.#activityEpoch, active: this.#activeReads > 0 }; }
|
|
106
129
|
constructor() {
|
|
@@ -147,9 +170,11 @@ export class ContainerOrchestrator {
|
|
|
147
170
|
let lifetime = this.#lifetimes.get(key);
|
|
148
171
|
if (!lifetime) this.#lifetimes.set(key, lifetime = {});
|
|
149
172
|
const isCurrent = () => this.#lifetimes.get(key) === lifetime && params.isCurrent?.() !== false && params.signal?.aborted !== true;
|
|
150
|
-
// Reads of one file run one after another,
|
|
151
|
-
//
|
|
152
|
-
//
|
|
173
|
+
// Reads of one file run one after another, ordered by playback-map urgency
|
|
174
|
+
// and then by interval size, so speculative extensions cannot sit ahead of
|
|
175
|
+
// an exact interval the viewer needs. Told to whoever asked, apart from
|
|
176
|
+
// the read itself, because the two call for different remedies
|
|
177
|
+
// (torrent-tv/meta#166, #ttv-245).
|
|
153
178
|
// A statement answered from what was already read reads and parses
|
|
154
179
|
// nothing, so it is answered beside the queue and holds nobody up: the
|
|
155
180
|
// picture's first input of an AVI waited about 4 s behind the sound's
|
|
@@ -159,9 +184,8 @@ export class ContainerOrchestrator {
|
|
|
159
184
|
// index. Keep it serial with other chapter reads, but out of the queue used
|
|
160
185
|
// to prepare playback inputs for this file.
|
|
161
186
|
const queueKey = statement === "chapters" ? `${key}:chapters` : key;
|
|
162
|
-
const previous = aside ? Promise.resolve() : this.#reads.get(queueKey) ?? Promise.resolve();
|
|
163
187
|
const askedAt = performance.now();
|
|
164
|
-
const
|
|
188
|
+
const perform = async () => {
|
|
165
189
|
if (!isCurrent()) return { result: { kind: "terminal", reason: "request-obsolete", requestId: params.requestId } };
|
|
166
190
|
const startedAt = performance.now();
|
|
167
191
|
const revision = params.onReadStart?.(statement);
|
|
@@ -171,18 +195,70 @@ export class ContainerOrchestrator {
|
|
|
171
195
|
params.onObserved?.({ statement, askedAt, startedAt, endedAt, queuedMs: startedAt - askedAt,
|
|
172
196
|
readMs: endedAt - startedAt, cached: aside, result: result.kind, reason: result.reason, ranges: result.ranges });
|
|
173
197
|
return { result, revision };
|
|
198
|
+
};
|
|
199
|
+
const reading = aside ? Promise.resolve().then(perform) : this.#queueRead(queueKey, { params, isCurrent, perform });
|
|
200
|
+
const { result, revision } = await reading;
|
|
201
|
+
if (!isCurrent()) return { kind: "terminal", reason: "request-obsolete", requestId: params.requestId };
|
|
202
|
+
if (result.kind === "needs-ranges") params.onNeedsRanges?.(result);
|
|
203
|
+
if (statement === "tracks" && result.kind === "result") params.onTracks?.(result.value);
|
|
204
|
+
await params.onReadResult?.(statement, result, revision);
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Run one media read at a time per file, in the map's order of urgency. */
|
|
209
|
+
#queueRead(key, { params, isCurrent, perform }) {
|
|
210
|
+
let queue = this.#reads.get(key);
|
|
211
|
+
if (!queue) this.#reads.set(key, queue = { active: false, pending: [] });
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
const entry = { params, isCurrent, perform, resolve, reject, readPriority: params.readPriority,
|
|
214
|
+
sequence: this.#readSequence++, started: false, abort: null };
|
|
215
|
+
const cancel = () => {
|
|
216
|
+
if (entry.started) return;
|
|
217
|
+
const index = queue.pending.indexOf(entry);
|
|
218
|
+
if (index < 0) return;
|
|
219
|
+
queue.pending.splice(index, 1);
|
|
220
|
+
params.signal?.removeEventListener("abort", entry.abort);
|
|
221
|
+
resolve({ result: { kind: "terminal", reason: "request-obsolete", requestId: params.requestId } });
|
|
222
|
+
this.#drainReadQueue(key, queue);
|
|
223
|
+
};
|
|
224
|
+
entry.abort = cancel;
|
|
225
|
+
queue.pending.push(entry);
|
|
226
|
+
queue.pending.sort(compareReadPriority);
|
|
227
|
+
if (params.signal?.aborted) cancel();
|
|
228
|
+
else params.signal?.addEventListener("abort", cancel, { once: true });
|
|
229
|
+
this.#drainReadQueue(key, queue);
|
|
174
230
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#drainReadQueue(key, queue) {
|
|
234
|
+
if (queue.active) return;
|
|
235
|
+
while (queue.pending.length > 0) {
|
|
236
|
+
const entry = queue.pending.shift();
|
|
237
|
+
entry.params.signal?.removeEventListener("abort", entry.abort);
|
|
238
|
+
if (!entry.isCurrent()) {
|
|
239
|
+
entry.resolve({ result: { kind: "terminal", reason: "request-obsolete", requestId: entry.params.requestId } });
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
entry.started = true;
|
|
243
|
+
queue.active = true;
|
|
244
|
+
Promise.resolve().then(entry.perform).then(entry.resolve, entry.reject).finally(() => {
|
|
245
|
+
queue.active = false;
|
|
246
|
+
if (queue.pending.length === 0 && this.#reads.get(key) === queue) this.#reads.delete(key);
|
|
247
|
+
else this.#drainReadQueue(key, queue);
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (this.#reads.get(key) === queue) this.#reads.delete(key);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#cancelQueuedReads(key) {
|
|
255
|
+
const queue = this.#reads.get(key);
|
|
256
|
+
if (!queue) return;
|
|
257
|
+
for (const entry of queue.pending.splice(0)) {
|
|
258
|
+
entry.params.signal?.removeEventListener("abort", entry.abort);
|
|
259
|
+
entry.resolve({ result: { kind: "terminal", reason: "request-obsolete", requestId: entry.params.requestId } });
|
|
185
260
|
}
|
|
261
|
+
if (this.#reads.get(key) === queue) this.#reads.delete(key);
|
|
186
262
|
}
|
|
187
263
|
|
|
188
264
|
/** Whether a statement of this file is answered from what was already read: its track table kept here, or the container's word. */
|
|
@@ -380,7 +456,8 @@ export class ContainerOrchestrator {
|
|
|
380
456
|
for (const key of this.#lifetimes.keys()) {
|
|
381
457
|
if (fileIndex === undefined ? key.startsWith(prefix) : key === `${sourceKey}:${fileIndex}`) {
|
|
382
458
|
this.#lifetimes.delete(key);
|
|
383
|
-
this.#
|
|
459
|
+
this.#cancelQueuedReads(key);
|
|
460
|
+
this.#cancelQueuedReads(`${key}:chapters`);
|
|
384
461
|
}
|
|
385
462
|
}
|
|
386
463
|
if (fileIndex === undefined) {
|