@torrent-tv/proxy 2.107.32 → 2.107.33
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,8 @@ 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
|
|
920
|
+
const encodeMetadataRequests = new WeakMap();
|
|
921
|
+
const containerOver = async ({ sourceKey, fileIndex, requestId, packetInterval, demand, readPriority, signal }) => {
|
|
921
922
|
const record = sourceRegistry.get(sourceKey);
|
|
922
923
|
if (!record) {
|
|
923
924
|
return null;
|
|
@@ -927,10 +928,34 @@ export async function startProxyServer({
|
|
|
927
928
|
if (!file || !(file.length > 0)) {
|
|
928
929
|
return null;
|
|
929
930
|
}
|
|
931
|
+
let metadataTracker = null;
|
|
932
|
+
let trackersForSignal = null;
|
|
933
|
+
const trackerKey = `${sourceKey}:${fileIndex}`;
|
|
934
|
+
if (readPriority?.requestId && signal) {
|
|
935
|
+
trackersForSignal = encodeMetadataRequests.get(signal);
|
|
936
|
+
if (!trackersForSignal) encodeMetadataRequests.set(signal, trackersForSignal = new Map());
|
|
937
|
+
metadataTracker = trackersForSignal.get(trackerKey);
|
|
938
|
+
if (!metadataTracker) {
|
|
939
|
+
metadataTracker = { statements: new Set(), abort: null };
|
|
940
|
+
metadataTracker.abort = () => {
|
|
941
|
+
for (const statement of metadataTracker.statements) {
|
|
942
|
+
void downloadMaps.withdrawMetadata(sourceKey, fileIndex, statement).catch(error =>
|
|
943
|
+
logger.warn(`encode input demand withdrawal failed: ${error.message}`));
|
|
944
|
+
}
|
|
945
|
+
metadataTracker.statements.clear();
|
|
946
|
+
trackersForSignal.delete(trackerKey);
|
|
947
|
+
if (trackersForSignal.size === 0) encodeMetadataRequests.delete(signal);
|
|
948
|
+
};
|
|
949
|
+
trackersForSignal.set(trackerKey, metadataTracker);
|
|
950
|
+
if (signal.aborted) metadataTracker.abort();
|
|
951
|
+
else signal.addEventListener("abort", metadataTracker.abort, { once: true });
|
|
952
|
+
}
|
|
953
|
+
}
|
|
930
954
|
const params = {
|
|
931
955
|
sourceKey,
|
|
932
956
|
fileIndex,
|
|
933
957
|
requestId,
|
|
958
|
+
signal,
|
|
934
959
|
packetInterval,
|
|
935
960
|
readPriority,
|
|
936
961
|
packetMemory: indexMemory.forFile(sourceKey, fileIndex),
|
|
@@ -969,9 +994,17 @@ export async function startProxyServer({
|
|
|
969
994
|
}
|
|
970
995
|
const finished = mediaReads.record(params, statement, result, revision.storage, revision.memory);
|
|
971
996
|
if (statement === "chapters" && result.passive) await downloadMaps.withdrawMetadata(sourceKey, fileIndex, statement);
|
|
972
|
-
else
|
|
973
|
-
|
|
974
|
-
|
|
997
|
+
else {
|
|
998
|
+
const metadataStatement = requestId ? `${statement}:${requestId}` : statement;
|
|
999
|
+
if (metadataTracker && result.kind === "needs-ranges") metadataTracker.statements.add(metadataStatement);
|
|
1000
|
+
if (metadataTracker && result.kind !== "needs-ranges") metadataTracker.statements.delete(metadataStatement);
|
|
1001
|
+
await downloadMaps.metadata({ sourceKey, fileIndex, statement: metadataStatement, result,
|
|
1002
|
+
...(statement === "chapters" ? { priority: 1, urgent: false, deadlineAt: null } : {}),
|
|
1003
|
+
...(demand ? { priority: demand.priority, urgent: demand.urgent, deadlineAt: demand.deadlineAt, interval: demand, leadSeconds: demand.leadSeconds ?? 0 } : {}),
|
|
1004
|
+
...(readPriority?.requestId ? { priority: readPriority.rank, urgent: readPriority.urgent === true,
|
|
1005
|
+
deadlineAt: Number.isFinite(readPriority.deadlineAt) ? readPriority.deadlineAt : Number.POSITIVE_INFINITY,
|
|
1006
|
+
scope: "preparation" } : {}) });
|
|
1007
|
+
}
|
|
975
1008
|
if (finished && result.kind === "result") {
|
|
976
1009
|
void downloadMaps.refresh(sourceKey, fileIndex).catch(error =>
|
|
977
1010
|
logger.warn(`download map source=${sourceKey} file=${fileIndex} metadata refresh failed: ${error?.message ?? error}`));
|
|
@@ -1042,7 +1075,8 @@ export async function startProxyServer({
|
|
|
1042
1075
|
// reassembled packets otherwise.
|
|
1043
1076
|
const files = [];
|
|
1044
1077
|
for (const [fileIndex, choices] of selected) {
|
|
1045
|
-
const params = await containerOver({ sourceKey: output.file.sourceKey, fileIndex,
|
|
1078
|
+
const params = await containerOver({ sourceKey: output.file.sourceKey, fileIndex,
|
|
1079
|
+
requestId: readPriority?.requestId, readPriority, signal });
|
|
1046
1080
|
if (!params) return { kind: "needs-source" };
|
|
1047
1081
|
if (isCurrent) params.isCurrent = isCurrent;
|
|
1048
1082
|
if (signal) params.signal = signal;
|
|
@@ -1145,6 +1179,7 @@ export async function startProxyServer({
|
|
|
1145
1179
|
.finally(() => releaseInput?.());
|
|
1146
1180
|
}
|
|
1147
1181
|
},
|
|
1182
|
+
repriceEncodeDemand: demand => downloadMaps.repriceRequest(demand.requestId, demand),
|
|
1148
1183
|
readEncodeRanges: async (source, ranges, maxBytes) => {
|
|
1149
1184
|
const torrent = torrentPool.knownTorrent(source.sourceKey);
|
|
1150
1185
|
return torrent ? torrentPool.readHeldRangesOf(torrent, source.fileIndex, ranges, maxBytes) : null;
|
|
@@ -56,6 +56,7 @@ export class EncodeInputs {
|
|
|
56
56
|
#capacity;
|
|
57
57
|
#urgent;
|
|
58
58
|
#rankAt;
|
|
59
|
+
#demandChanged;
|
|
59
60
|
#log;
|
|
60
61
|
#now;
|
|
61
62
|
/**
|
|
@@ -68,7 +69,7 @@ export class EncodeInputs {
|
|
|
68
69
|
#observeWait;
|
|
69
70
|
|
|
70
71
|
constructor({ resolve, readRanges, openWholeFile = null, heldRanges = null, reviseBudget, changed, failed, capacity = () => null, urgent = () => true,
|
|
71
|
-
rankAt = () => null, openOf = () => null, log = () => {}, now = Date.now, stretchCopied = () => {},
|
|
72
|
+
rankAt = () => null, demandChanged = () => {}, openOf = () => null, log = () => {}, now = Date.now, stretchCopied = () => {},
|
|
72
73
|
observeWait = null }) {
|
|
73
74
|
this.#stretchCopied = stretchCopied;
|
|
74
75
|
this.#observeWait = observeWait;
|
|
@@ -83,6 +84,7 @@ export class EncodeInputs {
|
|
|
83
84
|
this.#capacity = capacity;
|
|
84
85
|
this.#urgent = urgent;
|
|
85
86
|
this.#rankAt = rankAt;
|
|
87
|
+
this.#demandChanged = demandChanged;
|
|
86
88
|
this.#log = log;
|
|
87
89
|
this.#now = now;
|
|
88
90
|
}
|
|
@@ -204,7 +206,12 @@ export class EncodeInputs {
|
|
|
204
206
|
if (request.output !== output) continue;
|
|
205
207
|
const covered = windows.some(window => request.from <= window.to && request.to >= window.from);
|
|
206
208
|
const unused = starts !== null && request.result?.kind === "result" && !starts.has(request.from);
|
|
207
|
-
if (covered && !unused)
|
|
209
|
+
if (covered && !unused) {
|
|
210
|
+
const current = this.#readPriority(request, request.from, request.from);
|
|
211
|
+
Object.assign(request.readPriority ??= {}, current);
|
|
212
|
+
this.#demandChanged(output, request.readPriority);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
208
215
|
this.#note(request, covered ? "withdrawn: no live run ends before it and no start is placed there"
|
|
209
216
|
: "withdrawn: no wanted window covers it");
|
|
210
217
|
this.#requests.delete(key);
|
|
@@ -503,13 +510,15 @@ export class EncodeInputs {
|
|
|
503
510
|
* @param {object} request
|
|
504
511
|
* @param {number} from
|
|
505
512
|
* @param {number} to
|
|
506
|
-
* @returns {{ rank: number, serves: number, from: number, to: number }}
|
|
513
|
+
* @returns {{ rank: number, topRank: number, serves: number, from: number, to: number, urgent?: boolean, deadlineAt?: number, requestId: string }}
|
|
507
514
|
*/
|
|
508
515
|
#readPriority(request, from, to) {
|
|
509
516
|
const serves = request.serves ?? request.from;
|
|
510
517
|
const rank = this.#rankAt(request.output, serves);
|
|
511
518
|
return { rank: Number.isFinite(rank?.rank) ? rank.rank : 0, topRank: Number.isFinite(rank?.topRank) ? rank.topRank : 0,
|
|
512
|
-
|
|
519
|
+
...(typeof rank?.urgent === "boolean" ? { urgent: rank.urgent } : {}),
|
|
520
|
+
...(Number.isFinite(rank?.deadlineAt) ? { deadlineAt: rank.deadlineAt } : {}),
|
|
521
|
+
serves, from, to, requestId: `encode:${request.key}` };
|
|
513
522
|
}
|
|
514
523
|
|
|
515
524
|
/**
|
|
@@ -503,6 +503,11 @@ export class EncodeOrchestrator {
|
|
|
503
503
|
return this.#demand.rankOf(address, index);
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
/** The viewer-derived priority, urgency and due time for one output segment. */
|
|
507
|
+
demandAt(address, index) {
|
|
508
|
+
return this.#demand.demandAt(address, index);
|
|
509
|
+
}
|
|
510
|
+
|
|
506
511
|
/**
|
|
507
512
|
* How many encoder processes are running on this machine right now, over
|
|
508
513
|
* every output.
|
|
@@ -92,4 +92,27 @@ export class SegmentDemand {
|
|
|
92
92
|
}
|
|
93
93
|
return { rank, topRank };
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The viewer-derived demand for one output segment, including its due time.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} address
|
|
100
|
+
* @param {number} index
|
|
101
|
+
* @param {number} now
|
|
102
|
+
* @returns {{ rank: number, topRank: number, urgent: boolean, deadlineAt: number }}
|
|
103
|
+
*/
|
|
104
|
+
demandAt(address, index, now = Date.now()) {
|
|
105
|
+
const zones = this.mapOn(address);
|
|
106
|
+
let rank = 0, topRank = 0, urgent = false, deadlineAt = Number.POSITIVE_INFINITY;
|
|
107
|
+
for (const zone of zones) {
|
|
108
|
+
topRank = Math.max(topRank, Number.isFinite(zone.priority) ? zone.priority : 0);
|
|
109
|
+
if (index < zone.from || index > zone.to || !(zone.priority > 0)) continue;
|
|
110
|
+
rank = Math.max(rank, zone.priority);
|
|
111
|
+
urgent ||= zone.urgent === true;
|
|
112
|
+
const due = Number.isFinite(zone.deadlineAt) ? zone.deadlineAt
|
|
113
|
+
: Number.isFinite(zone.withinSeconds) ? now + Math.max(0, zone.withinSeconds) * 1000 : Number.POSITIVE_INFINITY;
|
|
114
|
+
deadlineAt = Math.min(deadlineAt, due);
|
|
115
|
+
}
|
|
116
|
+
return { rank, topRank, urgent, deadlineAt };
|
|
117
|
+
}
|
|
95
118
|
}
|
|
@@ -228,6 +228,7 @@ export function wireOutputs({
|
|
|
228
228
|
diagnosticsRoot = "",
|
|
229
229
|
memoryClaimant = null,
|
|
230
230
|
resolveEncodeInput = null,
|
|
231
|
+
repriceEncodeDemand = null,
|
|
231
232
|
readSourceMedia = null,
|
|
232
233
|
readMetadataActivity = null,
|
|
233
234
|
sourceInputsFor = null,
|
|
@@ -1191,7 +1192,12 @@ export function wireOutputs({
|
|
|
1191
1192
|
urgent: (output, index) => parts.encodeOrchestrator.wantedSegmentsOn(output.outputKey).some(window =>
|
|
1192
1193
|
index >= window.from && index <= window.to && window.urgent === true),
|
|
1193
1194
|
// The piece's rank in the output's own map, said with an input's state.
|
|
1194
|
-
rankAt: (output, index) => parts.encodeOrchestrator?.
|
|
1195
|
+
rankAt: (output, index) => parts.encodeOrchestrator?.demandAt?.(output.outputKey ?? "", index) ?? null,
|
|
1196
|
+
demandChanged: (_output, demand) => {
|
|
1197
|
+
void Promise.resolve(repriceEncodeDemand?.({ priority: demand.rank, urgent: demand.urgent === true,
|
|
1198
|
+
deadlineAt: Number.isFinite(demand.deadlineAt) ? demand.deadlineAt : Number.POSITIVE_INFINITY,
|
|
1199
|
+
requestId: demand.requestId })).catch(error => logger.warn(`encode input demand repricing failed: ${error.message}`));
|
|
1200
|
+
},
|
|
1195
1201
|
log: line => logger.info(line),
|
|
1196
1202
|
observeWait,
|
|
1197
1203
|
changed: (output, result) => {
|
|
@@ -238,6 +238,22 @@ export class DownloadMaps {
|
|
|
238
238
|
return this.#emit(file);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/** Reprice source metadata owned by one live encode input request. */
|
|
242
|
+
repriceRequest(requestId, demand) {
|
|
243
|
+
const changed = [];
|
|
244
|
+
for (const file of this.#files.values()) {
|
|
245
|
+
let fileChanged = false;
|
|
246
|
+
for (const metadata of file.metadata.values()) {
|
|
247
|
+
if (metadata.requestId !== requestId) continue;
|
|
248
|
+
if (Object.keys(demand).every(key => Object.is(metadata[key], demand[key]))) continue;
|
|
249
|
+
Object.assign(metadata, demand);
|
|
250
|
+
fileChanged = true;
|
|
251
|
+
}
|
|
252
|
+
if (fileChanged) changed.push(this.#emit(file));
|
|
253
|
+
}
|
|
254
|
+
return Promise.all(changed);
|
|
255
|
+
}
|
|
256
|
+
|
|
241
257
|
forget(sourceKey, fileIndex, { keepPreparation = false } = {}) {
|
|
242
258
|
const file = this.#file(sourceKey, fileIndex);
|
|
243
259
|
this.#setZones(file, []);
|