@torrent-tv/proxy 2.80.13 → 2.80.15
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/CHANGELOG.md +22 -0
- package/docs/download-architecture.md +42 -0
- package/docs/encode-architecture.md +87 -0
- package/package.json +1 -1
- package/routes/api/transcode-sessions/net-report/post.js +64 -53
- package/services/download/SwarmSelection.js +20 -4
- package/services/encode/EncodePlan.js +181 -40
- package/services/encode/SegmentStore.js +743 -718
- package/services/encode/run-costs.js +54 -3
- package/services/hls-session-manager.js +13 -14
- package/services/output/LiveOutputs.js +47 -1
- package/services/output/playlists.js +86 -11
- package/services/output/rates.js +125 -0
- package/services/piece-store/piece-lru.js +9 -1
- package/services/priority/PriorityOrchestrator.js +284 -278
- package/services/supply-margin.js +40 -1
- package/services/torrent-worker/piece-reader.js +9 -4
- package/services/torrent-worker/worker.js +5 -0
- package/services/viewer/Viewer.js +345 -297
- package/test/declared-rates.test.js +116 -0
- package/test/encode-plan.test.js +9 -3
- package/test/move-cost.test.js +138 -0
- package/test/piece-lru.test.js +5 -1
- package/test/supply-margin.test.js +25 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 2.80.15
|
|
2
|
+
|
|
3
|
+
- **Fix**: THE SPINNER CARAVAN, and its cause was one coercion. The priority map states `withinSeconds: null` for the film BEHIND the viewers — nobody is waiting there — and `deadlineReaderFor` read it through `Number()`, where `null` is 0. So the film a viewer had already passed was due IMMEDIATELY and was the most urgent material in the file. It bought encoders, and it took the run standing in front of the viewer because that run was the nearest body to it, and it did so again on every pass. Field 2026-09-08: 39 moves in one session, 24 of them between three adjacent numbers about 0.8 s apart, one viewer on a host affording three runs got three, and the picture stood still for 116.7 s in three interruptions, the worst 91.8 s. The map has said it plainly in every session's log; this line turned it into its opposite. Fourth time in this repository that the input to a calculation was not what the calculation assumed.
|
|
4
|
+
- **Fix**: The objective is the map's OWN rank order, as a vector compared position by position — one pair per rank the map states, most urgent first: how long anybody waits at that rank, then when the last number of it is made. It was three seconds-valued terms with the ten ranks collapsed into two buckets, and "never" for the film behind is the film's own length — 2024 s on a 48-minute file — which outvotes everything above it. No weights, and none possible: a weight would let seconds at one rank buy seconds at another and would be a number nobody measured. The map is the source of truth about what matters and it already says so.
|
|
5
|
+
- **Fix**: Residual work takes only spare capacity. A zone stating no deadline has nobody waiting in it, so a live run already standing in front of a viewer is never taken for it — taking it there means killing it, and the viewer waits out a cold start for film nobody asked for.
|
|
6
|
+
- **Fix**: An act must pay for itself. Where an arrangement is only reachable by killing a running encoder, a gain smaller than what that killing costs is not a gain. The field numbers are that close: driving from #58 to #59 means making two pieces, 1.89 s at 4.45x on a 4.2 s grid, against a cold start and one piece, 1.88 s — the move was faster by ten milliseconds, and every one of the 39 was individually correct. The margin is the measured cost of the act, never a chosen figure.
|
|
7
|
+
- **Fix**: The plan remembers what it is already carrying out. A staying run was priced as producing instantly however recently it had started; it is now priced at what it still has to go — the measured time to a first piece less the time it has been alive. A run 0.8 s old has 0.14 s left against 0.94 s to move it. One measured figure minus elapsed time, no state to keep.
|
|
8
|
+
- **Fix**: What a move costs is `Infinity` until something has been measured, because a move is irreversible and leaving the encoder alone is always available; placing one where there is none takes the unknown the other way, since the film gets made or it does not. And a run killed before producing anything is a measurement too — a lower bound on the first output — which is the only reading a thrash can supply, since every run in one is killed before it finishes anything.
|
|
9
|
+
- **Fix**: The average bitrate declared in the master came from a field that does not exist on a source file, so every variant was declared at the 400 kbit/s floor in 2.80.14 — nine times worse than the rule of thumb it replaced. It is read from where it is known: the torrent's own report of the file's length, with the probe's reading of the video stream covering the gap before that arrives.
|
|
10
|
+
- **New**: The piece store's own line names its holders, and `AHEAD` no longer reaches memory.
|
|
11
|
+
- **Chore**: `hls-session-manager.js` grew by four lines of wiring in its constructor, which the rule for that file does not allow. Stated rather than shuffled: the thing itself moved to `output/`, and what is left is two accessors handed over where wiring belongs.
|
|
12
|
+
|
|
13
|
+
## 2.80.14
|
|
14
|
+
|
|
15
|
+
- **Fix**: What the master playlist declares a variant carries is MEASURED. It was `height * height * 3.2` — for 1080 exactly the 3 732 480 the field declared for a file carrying 18.4 Mbit/s, five times more — and the comment justifying it said no measurement existed before encoding starts. Both do: the average is the file's own length over its duration, exact at session creation, and the peak is the biggest piece produced over the span it covers, equal to the average until one exists. A re-encoded height is declared at the cap we impose; a smaller height at the pixel share of the source's rate. `BANDWIDTH` and `AVERAGE-BANDWIDTH` are both emitted, as the specification names them. This is not cosmetic: the browser sizes its cushion in BYTES from `BANDWIDTH`, so 120 s asked bought 56 MB — 26 s of that film — and the deepest it ever held was 17.1 s. One expression, no case analysis: absent measurements and absent caps are written as the identity of the operation that consumes them (`services/output/rates.js`, eight checks).
|
|
16
|
+
- **Fix**: How much faster than realtime a step must run is computed from the share of the reading's time that was lost, over whole cycles. The model is unchanged and was always right; the two quantities fed into it were not — the WORST single interruption over the MEDIAN gap between interruptions, a maximum over a median, from populations that need not be the same events. Field 2026-09-08: 0.79 s over 0.01 s gave **158.60x** on a file already downloaded whole, and the quality budget refused every step against it forty times in one session. The same measurements as a share of time lost give 1.00x. The field case the old formula was written for is still caught: 3.50x refuses the 1.5 step that ran at 1.05x and stalled.
|
|
17
|
+
- **Fix**: Only what a read is stopped on reaches MEMORY. `AHEAD` used to, and it is exactly the speculative lead the swarm is told about: the priority map states one claimant per zone, so a film with seven zones arrived at the piece store as five holders — one `NEAR` and four `AHEAD` — on a session with two reads. Field: `5 reader(s) want 24 piece(s) of 25 the store may hold`, so every admission had to evict a piece somebody had declared and 100 of 1395 did, beside 6565 spills and 7575 revivals in 44 minutes at a median 0.0 s on disk. Raising the allowance does not touch it — a lead stated over the rest of the film grows to fill whatever it is given.
|
|
18
|
+
- **New**: The piece store NAMES its holders instead of counting them. Five read as five encoders on a session that had two, and choosing between narrowing the windows and raising the allowance was guesswork without the names.
|
|
19
|
+
- **New**: Whether the page is on screen, and whether the picture was pulled out of it into picture-in-picture, travel with every viewer report and live on the viewer beside where it stands. A hidden tab has its timers throttled — 800 ms of event-loop lag measured — so it asks for nothing and looked exactly like a viewer holding a full cushion; delivery stood still for the last six minutes of the field session and nothing said the tab had gone. Two facts and not one, because picture-in-picture is watching WITH the tab hidden. A viewer who is not on screen consumes nothing, which is what a paused one already meant: no deadline, and their place in the priority map kept.
|
|
20
|
+
- **Chore**: `hls-session-manager.js` is 5 lines shorter: the shape of the film the master declares — which heights splice, how big the source is, how it is packaged, what it carries — moved to `LiveOutputs.masterFactsOf`, which is the layer that holds it.
|
|
21
|
+
- **Chore**: Both architecture documents carry the new arithmetic and how to read it.
|
|
22
|
+
|
|
1
23
|
## 2.80.13
|
|
2
24
|
|
|
3
25
|
- **New**: Whether the priority map is being served IN ITS OWN ORDER is measured, for both things that read it. Until now the map's ranks were built, published, and then thrown away by everyone who acted on them — a map read backwards would have looked identical in every line this proxy writes. The measure is one wait recorded against the rank the map gave the thing waited for at the moment it was asked for, on one scale for both layers (`services/priority/WaitLedger.js`, `SegmentDemand.rankOf`, eight checks). The encoding's own state line carries `served[now N wait(s) median Xms worst Yms, soon …, later …]` and each `[hold]` line names the band it belonged to; a band that never waited prints `none` rather than a zero, because silence is not a good result. Read: long waits at `now` mean the urgent zone is not being served first, long waits lower down with none at `now` mean the zones are the wrong width.
|
|
@@ -95,6 +95,48 @@ the map put its piece in, and the `supply` line carries the table. Long waits at
|
|
|
95
95
|
`blocked` mean the urgent zone is too narrow; long waits further out mean the
|
|
96
96
|
lead is.
|
|
97
97
|
|
|
98
|
+
## What reaches MEMORY is not what reaches the swarm
|
|
99
|
+
|
|
100
|
+
`BLOCKED` and `NEAR` only. Memory holds what will be READ soon; the swarm is
|
|
101
|
+
told what will be DOWNLOADED soon, and the map states the second over the whole
|
|
102
|
+
rest of the film.
|
|
103
|
+
|
|
104
|
+
`AHEAD` used to reach memory too, and it is exactly the speculative lead. The
|
|
105
|
+
map states one claimant per zone, so a film with seven zones arrived at the piece
|
|
106
|
+
store as five separate holders — one `NEAR` and four `AHEAD` — each covering tens
|
|
107
|
+
of megabytes. Field 2026-09-08: `5 reader(s) want 24 piece(s) of 25 the store may
|
|
108
|
+
hold (widest window 17)`, on a session with exactly two reads. The union of what
|
|
109
|
+
was declared equalled the whole capacity, so every admission had to evict a piece
|
|
110
|
+
somebody had declared, and 100 of 1395 evictions did; beside that, 6565 spills
|
|
111
|
+
and 7575 revivals in 44 minutes with a median 0.0 s on disk.
|
|
112
|
+
|
|
113
|
+
Raising the allowance does not touch it: a lead stated over the rest of the film
|
|
114
|
+
grows to fill whatever memory it is given, and the ratio is unchanged.
|
|
115
|
+
|
|
116
|
+
The store's own line now NAMES its holders (`[priority-map:22:0 read:…]`) rather
|
|
117
|
+
than counting them, because the count read as five encoders on a session that
|
|
118
|
+
had two, and choosing between "narrow the windows" and "raise the allowance" was
|
|
119
|
+
guesswork without the names.
|
|
120
|
+
|
|
121
|
+
## How much faster than realtime a step must run
|
|
122
|
+
|
|
123
|
+
`1 / (1 - the share of the reading's time that was lost to waiting)`, over whole
|
|
124
|
+
cycles: from the first interruption's start to the last one's, which holds
|
|
125
|
+
exactly one running stretch per interruption in it.
|
|
126
|
+
|
|
127
|
+
The model is unchanged and was always right — if a fraction `f` of the time
|
|
128
|
+
delivers nothing, producing one second of film takes `1/(1 - f)` seconds. What
|
|
129
|
+
was wrong were the two quantities fed into it: the WORST single interruption
|
|
130
|
+
divided by the MEDIAN gap between interruptions, a maximum over a median, from
|
|
131
|
+
populations that need not be the same events. It asks what would happen if the
|
|
132
|
+
worst interruption recurred at the typical rate — a compound case that never
|
|
133
|
+
occurs — and it divides by a gap that goes to zero whenever interruptions arrive
|
|
134
|
+
in a burst.
|
|
135
|
+
|
|
136
|
+
Field 2026-09-08: 0.79 s over 0.01 s gave **158.60x** on a file already
|
|
137
|
+
downloaded whole, and the quality budget refused every step against it forty
|
|
138
|
+
times in one session. The same measurements as a share of time lost give 1.00x.
|
|
139
|
+
|
|
98
140
|
## The five levels
|
|
99
141
|
|
|
100
142
|
| level | what it is | stated |
|
|
@@ -166,6 +166,93 @@ chosen for the table.
|
|
|
166
166
|
The download half is measured the same way and on the same scale, so the two are
|
|
167
167
|
comparable: `download-architecture.md`.
|
|
168
168
|
|
|
169
|
+
## What the master playlist declares, and why it is not cosmetic
|
|
170
|
+
|
|
171
|
+
`BANDWIDTH` and `AVERAGE-BANDWIDTH` per variant, both measured
|
|
172
|
+
(`services/output/rates.js`):
|
|
173
|
+
|
|
174
|
+
- the average is the file's own length over its duration, exact and known when
|
|
175
|
+
the session is created;
|
|
176
|
+
- the peak is the biggest piece produced over the span it covers, and equals the
|
|
177
|
+
average until a piece exists — a ratio invented meanwhile would be the
|
|
178
|
+
fabrication this replaced;
|
|
179
|
+
- a re-encoded height is declared at the cap we impose, which is exact;
|
|
180
|
+
- a smaller height at the pixel share of the source's rate, which errs high, and
|
|
181
|
+
high is the safe direction.
|
|
182
|
+
|
|
183
|
+
It used to be `height * height * 3.2`. **The browser sizes its cushion in BYTES
|
|
184
|
+
from `BANDWIDTH`**, so a figure five times low makes the cushion five times
|
|
185
|
+
shallow: field 2026-09-08, 3.73 Mbit/s declared for a file carrying 18.4, 120 s
|
|
186
|
+
asked bought 56 MB — 26 s of that film — and the deepest the browser ever held
|
|
187
|
+
was 17.1 s. Inflating it is not the answer either: hls.js compares it against
|
|
188
|
+
its own estimate of the link to decide a level is unplayable, and its recovery
|
|
189
|
+
then moves level by itself, which does not honour our pinning.
|
|
190
|
+
|
|
191
|
+
## The objective is the map's own rank order
|
|
192
|
+
|
|
193
|
+
Not three terms in seconds. One pair per rank the map states, most urgent rank
|
|
194
|
+
first, compared position by position:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
[ late(100), done(100), late(99), done(99), … late(1), done(1) ], bodies, wasted
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`late(r)` is how long anybody waits past a deadline at rank `r`; `done(r)` is
|
|
201
|
+
when the last number of that rank is made. A difference at a higher rank settles
|
|
202
|
+
it and nothing lower can reopen it — which is the stated order: nobody stares at
|
|
203
|
+
a spinner; then the film in front is encoded as fast as it can be, band by band
|
|
204
|
+
as the map ranks them; then, with what is left over and only then, the film
|
|
205
|
+
behind, in case somebody seeks back.
|
|
206
|
+
|
|
207
|
+
**No weights, and none possible.** A weight would let seconds at one rank buy
|
|
208
|
+
seconds at another, and it would be a figure nobody measured. The map is the
|
|
209
|
+
source of truth about what matters and it already says so — ten ranks on a film,
|
|
210
|
+
p100 at the number a viewer is stopped on, doubling zones down to p91 for the far
|
|
211
|
+
tail, p1 for what lies behind them.
|
|
212
|
+
|
|
213
|
+
`bodies` ranks below every rank of the map, so spare capacity cannot buy an
|
|
214
|
+
encoder where the map is indifferent. `wasted` is the swarm's bill for anything
|
|
215
|
+
fetched twice.
|
|
216
|
+
|
|
217
|
+
## Three rules that stop an encoder being moved for nothing
|
|
218
|
+
|
|
219
|
+
**A zone with no deadline never takes a live run.** Residual work — the film
|
|
220
|
+
behind the viewers, kept in case somebody seeks back — is done with capacity
|
|
221
|
+
that is left over, and a run already standing in front of a viewer is not left
|
|
222
|
+
over.
|
|
223
|
+
|
|
224
|
+
**An act must pay for itself.** Where an arrangement is only reachable by killing
|
|
225
|
+
a running encoder, a gain smaller than what the killing costs is not a gain. The
|
|
226
|
+
margin is the measured cost of the act.
|
|
227
|
+
|
|
228
|
+
**The plan remembers what it is already carrying out.** A staying run is priced
|
|
229
|
+
at what it still has to go — the measured time to a first piece less the time it
|
|
230
|
+
has been alive — not at zero. A run 0.8 s old has 0.14 s left against 0.94 s to
|
|
231
|
+
move it; one working half a minute has nothing left, and a move then happens
|
|
232
|
+
exactly when the film it would reach sooner is worth the restart.
|
|
233
|
+
|
|
234
|
+
And what a move costs is `Infinity` until something has been measured: a move is
|
|
235
|
+
irreversible and leaving the encoder alone is always available. Placing one where
|
|
236
|
+
there is none takes the unknown the other way, because the film gets made or it
|
|
237
|
+
does not.
|
|
238
|
+
|
|
239
|
+
### What all three were for
|
|
240
|
+
|
|
241
|
+
Field 2026-09-08. The map states `withinSeconds: null` for the film behind the
|
|
242
|
+
viewers; `deadlineReaderFor` read it through `Number()`, where `null` is 0, so
|
|
243
|
+
that film was due IMMEDIATELY and was the most urgent material in the file. It
|
|
244
|
+
bought encoders and it took the run standing in front of the viewer, because
|
|
245
|
+
that run was the nearest body to it — 39 moves in one session, 24 of them between
|
|
246
|
+
three adjacent numbers about 0.8 s apart. One viewer on a host affording three
|
|
247
|
+
runs got three. The picture stood still for 116.7 s in three interruptions, the
|
|
248
|
+
worst of them 91.8 s.
|
|
249
|
+
|
|
250
|
+
The numbers are worth keeping because they are so close: driving from #58 to #59
|
|
251
|
+
means making TWO pieces, 1.89 s at 4.45x on a 4.2 s grid, against a cold start
|
|
252
|
+
and ONE piece, 0.94 + 0.94 = 1.88 s. Every one of those 39 moves was
|
|
253
|
+
individually the cheapest arrangement it was offered. That is the signature of an
|
|
254
|
+
optimiser with no memory, and the three rules above are what give it one.
|
|
255
|
+
|
|
169
256
|
## What is checked
|
|
170
257
|
|
|
171
258
|
`test/one-authority.test.js` holds the shape: one caller of `#startEncodeRun`,
|
package/package.json
CHANGED
|
@@ -1,53 +1,64 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Accept a viewer link report for a transcode session (adaptive bitrate).
|
|
3
|
-
* The browser measures its own data-channel throughput per segment fetch and
|
|
4
|
-
* posts a rolling median + its buffered seconds; the session manager's budget
|
|
5
|
-
* loop uses the latest report as the link-deficit downshift trigger.
|
|
6
|
-
*
|
|
7
|
-
* POST /api/transcode-sessions/:sessionId/net-report
|
|
8
|
-
* Body: { linkMbps: number, bufferedAheadSec: number,
|
|
9
|
-
* consumerId?: string, positionSeconds?: number }
|
|
10
|
-
*
|
|
11
|
-
* `consumerId` and `positionSeconds` say WHO is reporting and WHERE they are.
|
|
12
|
-
* A copied picture is one session shared by every viewer of it, so without them
|
|
13
|
-
* the proxy could only act on whichever viewer reported last. Both are
|
|
14
|
-
* optional: a browser that sends neither is treated exactly as before.
|
|
15
|
-
*
|
|
16
|
-
* Best-effort telemetry: invalid body → 400, unknown session → 404, ok → 204.
|
|
17
|
-
*
|
|
18
|
-
* @param {import("fastify").FastifyRequest} req
|
|
19
|
-
* @param {import("fastify").FastifyReply} reply
|
|
20
|
-
* @param {{ hlsSessionManager: import("../../../../services/hls-session-manager.js").HlsSessionManager }} deps
|
|
21
|
-
* @returns {Promise<void>}
|
|
22
|
-
*/
|
|
23
|
-
export async function handleApiTranscodeSessionNetReportPost(req, reply, { hlsSessionManager }) {
|
|
24
|
-
const sessionId = typeof req.params.sessionId === "string" ? req.params.sessionId : "";
|
|
25
|
-
const body = req.body && typeof req.body === "object" && !Array.isArray(req.body) ? req.body : {};
|
|
26
|
-
const linkMbps = Number(body.linkMbps);
|
|
27
|
-
const bufferedAheadSec = Number(body.bufferedAheadSec);
|
|
28
|
-
if (!sessionId || !Number.isFinite(linkMbps) || linkMbps <= 0 || !Number.isFinite(bufferedAheadSec) || bufferedAheadSec < 0) {
|
|
29
|
-
return reply.code(400).send({ error: "linkMbps (>0) and bufferedAheadSec (>=0) are required." });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Neither is required, and neither can make a report invalid: they are what
|
|
33
|
-
// the proxy uses to tell the viewers of one session apart, and a report
|
|
34
|
-
// without them is still a truthful reading of somebody's link.
|
|
35
|
-
const consumerId = typeof body.consumerId === "string" ? body.consumerId.trim() : "";
|
|
36
|
-
// Whether the picture is moving. Absent from a page that does not say, and
|
|
37
|
-
// then the viewer counts as playing, which is what every page meant before it
|
|
38
|
-
// could say otherwise.
|
|
39
|
-
const playing = typeof body.playing === "boolean" ? body.playing : undefined;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Accept a viewer link report for a transcode session (adaptive bitrate).
|
|
3
|
+
* The browser measures its own data-channel throughput per segment fetch and
|
|
4
|
+
* posts a rolling median + its buffered seconds; the session manager's budget
|
|
5
|
+
* loop uses the latest report as the link-deficit downshift trigger.
|
|
6
|
+
*
|
|
7
|
+
* POST /api/transcode-sessions/:sessionId/net-report
|
|
8
|
+
* Body: { linkMbps: number, bufferedAheadSec: number,
|
|
9
|
+
* consumerId?: string, positionSeconds?: number }
|
|
10
|
+
*
|
|
11
|
+
* `consumerId` and `positionSeconds` say WHO is reporting and WHERE they are.
|
|
12
|
+
* A copied picture is one session shared by every viewer of it, so without them
|
|
13
|
+
* the proxy could only act on whichever viewer reported last. Both are
|
|
14
|
+
* optional: a browser that sends neither is treated exactly as before.
|
|
15
|
+
*
|
|
16
|
+
* Best-effort telemetry: invalid body → 400, unknown session → 404, ok → 204.
|
|
17
|
+
*
|
|
18
|
+
* @param {import("fastify").FastifyRequest} req
|
|
19
|
+
* @param {import("fastify").FastifyReply} reply
|
|
20
|
+
* @param {{ hlsSessionManager: import("../../../../services/hls-session-manager.js").HlsSessionManager }} deps
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
export async function handleApiTranscodeSessionNetReportPost(req, reply, { hlsSessionManager }) {
|
|
24
|
+
const sessionId = typeof req.params.sessionId === "string" ? req.params.sessionId : "";
|
|
25
|
+
const body = req.body && typeof req.body === "object" && !Array.isArray(req.body) ? req.body : {};
|
|
26
|
+
const linkMbps = Number(body.linkMbps);
|
|
27
|
+
const bufferedAheadSec = Number(body.bufferedAheadSec);
|
|
28
|
+
if (!sessionId || !Number.isFinite(linkMbps) || linkMbps <= 0 || !Number.isFinite(bufferedAheadSec) || bufferedAheadSec < 0) {
|
|
29
|
+
return reply.code(400).send({ error: "linkMbps (>0) and bufferedAheadSec (>=0) are required." });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Neither is required, and neither can make a report invalid: they are what
|
|
33
|
+
// the proxy uses to tell the viewers of one session apart, and a report
|
|
34
|
+
// without them is still a truthful reading of somebody's link.
|
|
35
|
+
const consumerId = typeof body.consumerId === "string" ? body.consumerId.trim() : "";
|
|
36
|
+
// Whether the picture is moving. Absent from a page that does not say, and
|
|
37
|
+
// then the viewer counts as playing, which is what every page meant before it
|
|
38
|
+
// could say otherwise.
|
|
39
|
+
const playing = typeof body.playing === "boolean" ? body.playing : undefined;
|
|
40
|
+
// Whether the page is on screen, and whether the picture was pulled out of
|
|
41
|
+
// it. A hidden tab has its timers throttled, so it asks for nothing and looks
|
|
42
|
+
// exactly like a viewer holding a full cushion; picture-in-picture is the case
|
|
43
|
+
// that makes the distinction necessary, because there the tab is hidden and
|
|
44
|
+
// the viewer is watching. Absent from a page that does not say, and then the
|
|
45
|
+
// viewer is on screen, as every page meant before it could say otherwise.
|
|
46
|
+
const onScreen = typeof body.onScreen === "boolean" ? body.onScreen : undefined;
|
|
47
|
+
const inPictureInPicture =
|
|
48
|
+
typeof body.inPictureInPicture === "boolean" ? body.inPictureInPicture : undefined;
|
|
49
|
+
const positionSeconds = Number(body.positionSeconds);
|
|
50
|
+
const recorded = hlsSessionManager.recordNetReport(sessionId, {
|
|
51
|
+
linkMbps,
|
|
52
|
+
bufferedAheadSec,
|
|
53
|
+
consumerId,
|
|
54
|
+
playing,
|
|
55
|
+
onScreen,
|
|
56
|
+
inPictureInPicture,
|
|
57
|
+
positionSeconds:
|
|
58
|
+
Number.isFinite(positionSeconds) && positionSeconds >= 0 ? positionSeconds : undefined
|
|
59
|
+
});
|
|
60
|
+
if (!recorded) {
|
|
61
|
+
return reply.code(404).send({ error: "Transcode session was not found." });
|
|
62
|
+
}
|
|
63
|
+
return reply.code(204).send();
|
|
64
|
+
}
|
|
@@ -206,9 +206,25 @@ export class SwarmSelection {
|
|
|
206
206
|
* rebuild the second. Now there is one statement and two views of it, both
|
|
207
207
|
* computed here.
|
|
208
208
|
*
|
|
209
|
-
*
|
|
210
|
-
* the
|
|
211
|
-
*
|
|
209
|
+
* WHAT WILL BE READ SOON, WHICH IS NOT WHAT WILL BE DOWNLOADED SOON. Memory
|
|
210
|
+
* holds the first; the swarm is told the second; and the priority map states
|
|
211
|
+
* the second, over the whole rest of the film.
|
|
212
|
+
*
|
|
213
|
+
* `AHEAD` used to reach memory, and it is exactly the speculative lead: the
|
|
214
|
+
* map states one claimant per zone, so on a film with seven zones one `NEAR`
|
|
215
|
+
* and four `AHEAD` arrived here as five separate holders, each covering tens
|
|
216
|
+
* of megabytes. Field 2026-09-08: the store reported `5 reader(s) want 24
|
|
217
|
+
* piece(s) of 25 the store may hold (widest window 17)` — the union of what
|
|
218
|
+
* was declared equalled the whole capacity, so every admission had to evict a
|
|
219
|
+
* piece somebody had declared, and 100 of 1395 evictions did. Beside that,
|
|
220
|
+
* 6565 spills and 7575 revivals in 44 minutes with a median 0.0 s on disk, and
|
|
221
|
+
* 2138 h264 parse errors on a picture that was being COPIED.
|
|
222
|
+
*
|
|
223
|
+
* Raising the allowance does not touch it: a lead stated over the rest of the
|
|
224
|
+
* film grows to fill whatever memory it is given, and the ratio is unchanged.
|
|
225
|
+
* What belongs in memory is what a READ is stopped on and the little in front
|
|
226
|
+
* of it — the levels a read itself states — and those are `BLOCKED` and
|
|
227
|
+
* `NEAR`.
|
|
212
228
|
*
|
|
213
229
|
* @returns {void}
|
|
214
230
|
*/
|
|
@@ -218,7 +234,7 @@ export class SwarmSelection {
|
|
|
218
234
|
return;
|
|
219
235
|
}
|
|
220
236
|
const holding = new Set();
|
|
221
|
-
for (const urgency of [Urgency.BLOCKED, Urgency.NEAR
|
|
237
|
+
for (const urgency of [Urgency.BLOCKED, Urgency.NEAR]) {
|
|
222
238
|
for (const window of this.#register.at(urgency)) {
|
|
223
239
|
const range = this.#piecesFor(window);
|
|
224
240
|
if (!range) {
|