@torrent-tv/proxy 2.101.11 → 2.101.13
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 +1 -1
- package/routes/api/sources/post.js +3 -2
- package/services/server/OutputLifecycle.js +3 -8
- package/services/server/SegmentServing.js +3 -4
- package/services/server/ViewerRequests.js +15 -6
- package/services/viewer/Viewers.js +13 -1
- package/services/viewer/choices.js +10 -9
- package/services/viewer/report-intake.js +5 -4
package/package.json
CHANGED
|
@@ -31,8 +31,9 @@ export async function handleApiSourcesPost(req, reply, { sourceRegistry, viewers
|
|
|
31
31
|
return reply.code(400).send({ error: "sourceType and source are required." });
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const sourceKey = await sourceRegistry.upsert(sourceType, source);
|
|
35
34
|
const consumerId = typeof payload.consumerId === "string" ? payload.consumerId.trim() : "";
|
|
36
|
-
|
|
35
|
+
const viewer = consumerId ? viewers?.present(consumerId) : null;
|
|
36
|
+
const sourceKey = await sourceRegistry.upsert(sourceType, source);
|
|
37
|
+
if (viewer && viewers.get(consumerId) === viewer) viewers.selectsSource(consumerId, sourceKey);
|
|
37
38
|
return reply.send({ sourceKey, playbackMapVersion: 1 });
|
|
38
39
|
}
|
|
@@ -403,17 +403,12 @@ export class OutputLifecycle {
|
|
|
403
403
|
if (typeof consumerId !== "string" || consumerId.length === 0) {
|
|
404
404
|
return 0;
|
|
405
405
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return 0;
|
|
410
|
-
}
|
|
411
|
-
// Copied before anything is released: releasing walks the same set.
|
|
412
|
-
const outputs = [...watched];
|
|
406
|
+
// Presence ends synchronously, before disposal waits for a child process.
|
|
407
|
+
// Late reports and preparations must not rejoin this viewer meanwhile.
|
|
408
|
+
const outputs = this.#host.viewers.hasGone(consumerId);
|
|
413
409
|
for (const outputId of outputs) {
|
|
414
410
|
await this.releaseSessionConsumer(outputId, consumerId, because);
|
|
415
411
|
}
|
|
416
|
-
this.#host.viewers.hasGone(consumerId);
|
|
417
412
|
return outputs.length;
|
|
418
413
|
}
|
|
419
414
|
|
|
@@ -904,9 +904,8 @@ export class SegmentServing {
|
|
|
904
904
|
* backwards several times a second — measured 2026-09-13, 77 encoder starts
|
|
905
905
|
* and 141 stops in six minutes while both viewers sat frozen.
|
|
906
906
|
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
* look-ahead, not a chosen interval.
|
|
907
|
+
* Presence is established by arrival or an explicit opening and ends with
|
|
908
|
+
* the connection. A queued request cannot restore a viewer who has left.
|
|
910
909
|
*
|
|
911
910
|
* @param {HlsSession} session
|
|
912
911
|
* @param {string} consumerId
|
|
@@ -917,7 +916,7 @@ export class SegmentServing {
|
|
|
917
916
|
if (!consumerId) {
|
|
918
917
|
return;
|
|
919
918
|
}
|
|
920
|
-
this.#host.viewers.
|
|
919
|
+
this.#host.viewers.join(session, consumerId)?.seen();
|
|
921
920
|
}
|
|
922
921
|
|
|
923
922
|
/**
|
|
@@ -121,7 +121,13 @@ export class ViewerRequests {
|
|
|
121
121
|
// What this viewer's link last measured, where the request does not say:
|
|
122
122
|
// a viewer who comes back to open another film has a link this proxy has
|
|
123
123
|
// already measured, and the output they are given is judged by it.
|
|
124
|
-
|
|
124
|
+
let known = consumerId ? this.#host.viewers.get(consumerId) : null;
|
|
125
|
+
const requireViewing = () => {
|
|
126
|
+
request.signal?.throwIfAborted();
|
|
127
|
+
if (known && this.#host.viewers.get(consumerId) !== known) {
|
|
128
|
+
throw new DOMException("The viewer left while the output was opening.", "AbortError");
|
|
129
|
+
}
|
|
130
|
+
};
|
|
125
131
|
const viewerLinkMbps = request.viewerLinkMbps ?? known?.linkReading()?.linkMbps ?? null;
|
|
126
132
|
const startPositionSeconds = request.startPositionSeconds ?? 0;
|
|
127
133
|
// Whether they were already on the output this request lands on, read at
|
|
@@ -136,13 +142,14 @@ export class ViewerRequests {
|
|
|
136
142
|
// state nothing has every encoder on it stopped.
|
|
137
143
|
const claim = consumerId
|
|
138
144
|
? (candidate) => {
|
|
139
|
-
|
|
145
|
+
requireViewing();
|
|
140
146
|
alreadyOn = this.#host.viewers.forOutput(candidate).has(consumerId);
|
|
141
147
|
const answer = alreadyOn
|
|
142
148
|
? { admitted: true, reason: "already watching it", speedX: null }
|
|
143
149
|
: this.#host.admitsWatching(candidate);
|
|
144
150
|
if (answer.admitted) {
|
|
145
|
-
|
|
151
|
+
known = this.#host.viewers.of(candidate, consumerId);
|
|
152
|
+
this.placeViewer(candidate, known, startPositionSeconds);
|
|
146
153
|
}
|
|
147
154
|
return answer;
|
|
148
155
|
}
|
|
@@ -152,7 +159,7 @@ export class ViewerRequests {
|
|
|
152
159
|
viewerLinkMbps,
|
|
153
160
|
claim
|
|
154
161
|
});
|
|
155
|
-
|
|
162
|
+
requireViewing();
|
|
156
163
|
if (consumerId) {
|
|
157
164
|
const joined = existed && !alreadyOn;
|
|
158
165
|
// What THIS viewer wants of the sound, which an output they are joining
|
|
@@ -257,7 +264,8 @@ export class ViewerRequests {
|
|
|
257
264
|
if (!output || !consumerId) {
|
|
258
265
|
return;
|
|
259
266
|
}
|
|
260
|
-
|
|
267
|
+
const viewer = this.#host.viewers.join(output, consumerId);
|
|
268
|
+
if (viewer) this.placeViewer(output, viewer, positionSeconds);
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
placeViewer(session, viewer, positionSeconds) {
|
|
@@ -320,7 +328,8 @@ export class ViewerRequests {
|
|
|
320
328
|
// from where the viewers are, and both orchestrators read the map.
|
|
321
329
|
// A seek that names nobody moves nobody: the route refuses it before it
|
|
322
330
|
// gets here.
|
|
323
|
-
const viewer = this.#host.viewers.
|
|
331
|
+
const viewer = this.#host.viewers.join(named, consumerId);
|
|
332
|
+
if (!viewer) return false;
|
|
324
333
|
viewer.moveTo(positionSeconds);
|
|
325
334
|
// WHICH VIEWING THEIR REQUESTS NOW BELONG TO, as the page states it.
|
|
326
335
|
//
|
|
@@ -97,7 +97,9 @@ export class Viewers {
|
|
|
97
97
|
if (!Array.isArray(indices) || indices.some(index => !Number.isSafeInteger(index) || index < 0)) {
|
|
98
98
|
throw new TypeError("Visible files need source indices.");
|
|
99
99
|
}
|
|
100
|
-
const viewer = this.
|
|
100
|
+
const viewer = this.get(consumerId);
|
|
101
|
+
if (!viewer?.isPresent() || viewer.source?.sourceKey !== sourceKey) return null;
|
|
102
|
+
viewer.seen(now);
|
|
101
103
|
const visible = [...new Set(indices)];
|
|
102
104
|
if (JSON.stringify(viewer.source.visibleFileIndices) !== JSON.stringify(visible)) {
|
|
103
105
|
viewer.source.visibleFileIndices = visible;
|
|
@@ -179,6 +181,16 @@ export class Viewers {
|
|
|
179
181
|
return this.#byId.get(consumerId) ?? null;
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Add an output to an existing viewing. A request or preparation completing
|
|
186
|
+
* after departure cannot establish presence again; only arrival or a new
|
|
187
|
+
* explicit opening may do that through `present` or `of`.
|
|
188
|
+
*/
|
|
189
|
+
join(output, consumerId, now = Date.now()) {
|
|
190
|
+
const viewer = this.get(consumerId);
|
|
191
|
+
return viewer?.isPresent() ? this.of(output, consumerId, now) : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
182
194
|
sourceKeys() {
|
|
183
195
|
return new Set([...this.#byId.values()].filter(viewer => viewer.isPresent() && viewer.source)
|
|
184
196
|
.map(viewer => viewer.source.sourceKey));
|
|
@@ -96,7 +96,8 @@ export function audioChoiceOf(viewers, output, consumerId) {
|
|
|
96
96
|
* @returns {void}
|
|
97
97
|
*/
|
|
98
98
|
export function chooseAudioTrack(viewers, output, consumerId, choice) {
|
|
99
|
-
viewers.
|
|
99
|
+
const viewer = viewers.join(output, consumerId);
|
|
100
|
+
if (viewer) viewer.audio = { trackIndex: choice.trackIndex, transcode: choice.transcode === true };
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/**
|
|
@@ -122,7 +123,8 @@ export function stepOnScreenOf(viewers, output, consumerId) {
|
|
|
122
123
|
* @returns {void}
|
|
123
124
|
*/
|
|
124
125
|
export function noteStepOnScreen(viewers, output, consumerId, stepId) {
|
|
125
|
-
viewers.
|
|
126
|
+
const viewer = viewers.join(output, consumerId);
|
|
127
|
+
if (viewer) viewer.activeVariantId = stepId;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
/**
|
|
@@ -148,7 +150,7 @@ export function noteStepBeingWarmed(viewers, output, consumerId, stepId) {
|
|
|
148
150
|
// Clearing does not make a watcher. Warming one FOR somebody is them
|
|
149
151
|
// watching; saying nothing is being warmed for a person who watches nothing
|
|
150
152
|
// would put them on this output for the sake of a null.
|
|
151
|
-
const viewer = stepId ? viewers.
|
|
153
|
+
const viewer = stepId ? viewers.join(output, consumerId) : on(viewers, output, consumerId);
|
|
152
154
|
if (viewer) {
|
|
153
155
|
// When the warming of THIS step began: kept across a repeat of the same
|
|
154
156
|
// step, started afresh for another one, gone with it.
|
|
@@ -192,16 +194,15 @@ export function audioBeingWarmedOf(viewers, output, consumerId) {
|
|
|
192
194
|
* @returns {void}
|
|
193
195
|
*/
|
|
194
196
|
export function noteAudioBeingWarmed(viewers, output, consumerId, renditionId) {
|
|
195
|
-
const viewer = renditionId ? viewers.
|
|
197
|
+
const viewer = renditionId ? viewers.join(output, consumerId) : on(viewers, output, consumerId);
|
|
196
198
|
if (viewer) {
|
|
197
199
|
viewer.warmingAudioId = renditionId;
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* before it can ask for a segment would never be made.
|
|
204
|
+
* A present viewer is watching this output. Asking for its init or a segment
|
|
205
|
+
* adds the output to their viewing; a late request cannot establish presence.
|
|
205
206
|
*
|
|
206
207
|
* @param {object} viewers
|
|
207
208
|
* @param {object} output
|
|
@@ -209,7 +210,7 @@ export function noteAudioBeingWarmed(viewers, output, consumerId, renditionId) {
|
|
|
209
210
|
* @returns {void}
|
|
210
211
|
*/
|
|
211
212
|
export function watches(viewers, output, consumerId) {
|
|
212
|
-
viewers.
|
|
213
|
+
viewers.join(output, consumerId);
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
/**
|
|
@@ -222,7 +223,7 @@ export function watches(viewers, output, consumerId) {
|
|
|
222
223
|
* @returns {void}
|
|
223
224
|
*/
|
|
224
225
|
export function placeOn(viewers, output, consumerId, seconds) {
|
|
225
|
-
viewers.
|
|
226
|
+
viewers.join(output, consumerId)?.moveTo(seconds);
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
/**
|
|
@@ -23,13 +23,12 @@ import { activeOutputFor } from "./active-output.js";
|
|
|
23
23
|
*
|
|
24
24
|
* @param {object} params
|
|
25
25
|
* @param {{ get: (id: string) => object | undefined }} params.outputs - Every live output, by id.
|
|
26
|
-
* @param {{
|
|
26
|
+
* @param {{ join: (session: object, consumerId: string) => import("./Viewer.js").Viewer | null }} params.viewers
|
|
27
27
|
* @param {string} params.sessionId - The session the page addresses, which is
|
|
28
28
|
* always the picture's: the browser is not told which rung it is on.
|
|
29
29
|
* @param {object} params.report - What they said about themselves.
|
|
30
30
|
* @param {number} [params.now]
|
|
31
|
-
* @returns {boolean} False when
|
|
32
|
-
* reporting into one that has been disposed.
|
|
31
|
+
* @returns {boolean} False when the session or its viewer has gone.
|
|
33
32
|
*/
|
|
34
33
|
export function recordViewerReport({ outputs, viewers, sessionId, report, now = Date.now() }) {
|
|
35
34
|
const named = outputs.get(sessionId);
|
|
@@ -41,6 +40,8 @@ export function recordViewerReport({ outputs, viewers, sessionId, report, now =
|
|
|
41
40
|
// The stream on screen is the reporter's own: with two viewers on two rungs,
|
|
42
41
|
// one report says nothing about the other's encoder.
|
|
43
42
|
const session = activeOutputFor({ base: named, consumerId, outputs, viewers });
|
|
44
|
-
viewers.
|
|
43
|
+
const viewer = viewers.join(session, consumerId);
|
|
44
|
+
if (!viewer) return false;
|
|
45
|
+
viewer.report(report, now);
|
|
45
46
|
return true;
|
|
46
47
|
}
|