@torrent-tv/proxy 2.106.0 → 2.106.1
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
|
@@ -23,6 +23,7 @@ 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
25
|
import { SourcePreparation, preparationInterval } from "./services/viewer/SourcePreparation.js";
|
|
26
|
+
import { publishPlaybackBeforePreparationRefresh } from "./services/server/publish-playback-before-preparation-refresh.js";
|
|
26
27
|
import { handleHealthzGet } from "./routes/healthz/get.js";
|
|
27
28
|
import { handleApiDeliverySinkGet } from "./routes/api/delivery-sink/get.js";
|
|
28
29
|
import { handleApiLinkProbeGet } from "./routes/api/link-probe/get.js";
|
|
@@ -1213,7 +1214,6 @@ export async function startProxyServer({
|
|
|
1213
1214
|
setDownloadRate: (bytesPerSecond, because) => torrentPool.setDownloadRate?.(bytesPerSecond, because),
|
|
1214
1215
|
setSpeculativeHold: (held, because) => torrentPool.setSpeculativeHold?.(held, because),
|
|
1215
1216
|
setPriorityMap: async ({ sourceKey, fileIndex, durationSeconds, zones }) => {
|
|
1216
|
-
void sourcePreparation?.refresh();
|
|
1217
1217
|
const record = sourceRegistry.get(sourceKey);
|
|
1218
1218
|
if (!record) {
|
|
1219
1219
|
return;
|
|
@@ -1224,9 +1224,13 @@ export async function startProxyServer({
|
|
|
1224
1224
|
if (!outputParts.viewers.forSource(sourceKey).length) probeReads.forget(sourceKey, fileIndex);
|
|
1225
1225
|
await downloadMaps.forget(sourceKey, fileIndex, { keepPreparation: true });
|
|
1226
1226
|
playbackProjections.get(`${sourceKey}:${fileIndex}`)?.clear();
|
|
1227
|
+
void sourcePreparation?.refresh();
|
|
1227
1228
|
return;
|
|
1228
1229
|
}
|
|
1229
|
-
await
|
|
1230
|
+
await publishPlaybackBeforePreparationRefresh(
|
|
1231
|
+
() => downloadMaps.playback({ sourceKey, fileIndex, durationSeconds, zones }),
|
|
1232
|
+
() => { void sourcePreparation?.refresh(); }
|
|
1233
|
+
);
|
|
1230
1234
|
mediaReads.retain(sourceKey, fileIndex, params => !params.requestId?.startsWith("download:") ||
|
|
1231
1235
|
zones.some(zone => zone.from === params.downloadInterval?.from && zone.to === params.downloadInterval?.to));
|
|
1232
1236
|
} catch (error) {
|
|
@@ -1375,6 +1379,7 @@ export async function startProxyServer({
|
|
|
1375
1379
|
const held = await torrentPool.heldRangesOf(torrent, work.fileIndex);
|
|
1376
1380
|
return work.inputRanges.every(([start, end]) => held.some(([from, to]) => from <= start && to >= end));
|
|
1377
1381
|
},
|
|
1382
|
+
playbackAppliedFor: (sourceKey, fileIndex) => downloadMaps.hasAppliedPlayback(sourceKey, fileIndex),
|
|
1378
1383
|
readyChanged: (work, ready) => logger.info(`source urgent request=${work.requestId} viewer=${work.ownerId} ` +
|
|
1379
1384
|
`source=${work.sourceKey} file=${work.fileIndex} inputReady=${ready}`),
|
|
1380
1385
|
inspect: async work => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep source preparation stated until the viewer's playback map is applied.
|
|
3
|
+
*
|
|
4
|
+
* @param {() => Promise<unknown>} publishPlayback
|
|
5
|
+
* @param {() => unknown} refreshPreparation
|
|
6
|
+
* @returns {Promise<void>}
|
|
7
|
+
*/
|
|
8
|
+
export async function publishPlaybackBeforePreparationRefresh(publishPlayback, refreshPreparation) {
|
|
9
|
+
await publishPlayback();
|
|
10
|
+
refreshPreparation();
|
|
11
|
+
}
|
|
@@ -162,6 +162,11 @@ export class DownloadMaps {
|
|
|
162
162
|
return this.#files.get(`${sourceKey}:${fileIndex}`)?.epoch ?? 0;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
hasAppliedPlayback(sourceKey, fileIndex) {
|
|
166
|
+
const file = this.#files.get(`${sourceKey}:${fileIndex}`);
|
|
167
|
+
return Boolean(file?.zones.length && file.shape && file.appliedShape === file.shape);
|
|
168
|
+
}
|
|
169
|
+
|
|
165
170
|
native({ sourceKey, fileIndex, zones }) {
|
|
166
171
|
const file = this.#file(sourceKey, fileIndex);
|
|
167
172
|
file.nativeZones = zones;
|
|
@@ -325,7 +330,12 @@ export class DownloadMaps {
|
|
|
325
330
|
const answeredAt = this.#clock();
|
|
326
331
|
// A torrent not yet in the thread applies nothing, so this map was not
|
|
327
332
|
// delivered and the next one, however alike, has to be.
|
|
328
|
-
if (reply?.appliedMs === null
|
|
333
|
+
if (reply?.appliedMs === null) {
|
|
334
|
+
if (file.shape === shape) file.shape = null;
|
|
335
|
+
if (file.appliedShape === shape) file.appliedShape = null;
|
|
336
|
+
} else {
|
|
337
|
+
file.appliedShape = shape;
|
|
338
|
+
}
|
|
329
339
|
// The window open now, which a report since this map was asked for may have started.
|
|
330
340
|
const stats = this.#statsOf(file);
|
|
331
341
|
stats.sent += 1;
|
|
@@ -345,6 +355,7 @@ export class DownloadMaps {
|
|
|
345
355
|
// sent, and leaves nothing to compare the next one against.
|
|
346
356
|
file.pending = pending.catch(() => {
|
|
347
357
|
if (file.shape === shape) file.shape = null;
|
|
358
|
+
if (file.appliedShape === shape) file.appliedShape = null;
|
|
348
359
|
});
|
|
349
360
|
return pending;
|
|
350
361
|
}
|
|
@@ -38,6 +38,7 @@ export class SourcePreparation {
|
|
|
38
38
|
#reprice;
|
|
39
39
|
#inputReady;
|
|
40
40
|
#readyChanged;
|
|
41
|
+
#playbackAppliedFor;
|
|
41
42
|
#work = new Map();
|
|
42
43
|
#revision = 0;
|
|
43
44
|
#completedRevision = null;
|
|
@@ -45,7 +46,8 @@ export class SourcePreparation {
|
|
|
45
46
|
#candidateAbort = new AbortController();
|
|
46
47
|
|
|
47
48
|
constructor({ viewers, candidatesFor, relatedFilesFor = () => [], priorityFor = (_viewer, priority) => priority,
|
|
48
|
-
inputReady = async () => false, readyChanged = () => {},
|
|
49
|
+
inputReady = async () => false, readyChanged = () => {}, playbackAppliedFor = () => true,
|
|
50
|
+
inspect, withdraw, reprice = () => {}, failed }) {
|
|
49
51
|
this.#viewers = viewers;
|
|
50
52
|
this.#candidates = candidatesFor;
|
|
51
53
|
this.#related = relatedFilesFor;
|
|
@@ -56,6 +58,7 @@ export class SourcePreparation {
|
|
|
56
58
|
this.#reprice = reprice;
|
|
57
59
|
this.#inputReady = inputReady;
|
|
58
60
|
this.#readyChanged = readyChanged;
|
|
61
|
+
this.#playbackAppliedFor = playbackAppliedFor;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
refresh() {
|
|
@@ -80,8 +83,13 @@ export class SourcePreparation {
|
|
|
80
83
|
|
|
81
84
|
/** Current owners determine priority; old work never restores a withdrawn read. */
|
|
82
85
|
demandFor(work) {
|
|
86
|
+
const handoffFileIndex = work.selected || (work.role === "source-rest" && work.ownerFileIndex === work.fileIndex)
|
|
87
|
+
? work.fileIndex : null;
|
|
88
|
+
const opening = Number.isInteger(handoffFileIndex) && this.#viewers.forSource(work.sourceKey).some(viewer =>
|
|
89
|
+
(work.ownerId === undefined || viewer.id === work.ownerId) && viewer.source.selectedFileIndex === handoffFileIndex &&
|
|
90
|
+
viewer.outputs.size > 0 && !this.#playbackAppliedFor(work.sourceKey, handoffFileIndex));
|
|
83
91
|
if (work.selected && work.statement === "packets" &&
|
|
84
|
-
work.positions !== this.#positionsFor(work.sourceKey, work.fileIndex, work.ownerId)) return null;
|
|
92
|
+
work.positions !== this.#positionsFor(work.sourceKey, work.fileIndex, work.ownerId, opening)) return null;
|
|
85
93
|
if (work.role === "subtitle-embedded" && ["packets", "subtitle-cues"].includes(work.statement) &&
|
|
86
94
|
work.positions !== this.#positionsFor(work.sourceKey, work.fileIndex, work.ownerId, true)) return null;
|
|
87
95
|
const owners = this.#viewers.forSource(work.sourceKey).filter(viewer => {
|
|
@@ -90,9 +98,9 @@ export class SourcePreparation {
|
|
|
90
98
|
viewer.subtitle.trackIndex !== work.trackIndex)) return false;
|
|
91
99
|
const selected = viewer.source.selectedFileIndex;
|
|
92
100
|
if (work.ownerFileIndex !== undefined) return selected === work.ownerFileIndex &&
|
|
93
|
-
(work.role !== "source-rest" || viewer.outputs.size === 0);
|
|
101
|
+
(work.role !== "source-rest" || viewer.outputs.size === 0 || opening);
|
|
94
102
|
if (work.statement !== "packets") return selected === null || selected === work.fileIndex;
|
|
95
|
-
if (selected !== null) return selected === work.fileIndex && viewer.outputs.size === 0 && work.selected;
|
|
103
|
+
if (selected !== null) return selected === work.fileIndex && (viewer.outputs.size === 0 || opening) && work.selected;
|
|
96
104
|
return !work.selected && viewer.source.visibleFileIndices.includes(work.fileIndex);
|
|
97
105
|
});
|
|
98
106
|
if (!owners.length) return null;
|