@torrent-tv/proxy 2.21.0 → 2.22.0
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 +9 -0
- package/package.json +1 -1
- package/services/hls-session-manager.js +85 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 2.22.0
|
|
2
|
+
|
|
3
|
+
- **Fix**: What a rung is OFFERED on is the startup measurement again, not the figure learned from a live session. The startup one is taken on a quiet machine against known clips and does not move; the learned one moves with whatever else the box was doing that second, and three field sessions in a row show the price of that: decoding learned at 0.87x, then at 1.34-1.57x, against calibration's 2.6x — each reading refusing another rung until the offer held a single height and the quality menu vanished with it.
|
|
4
|
+
- **New**: A live reading keeps the one thing it is authority on — itself. A rung that has actually been seen running below realtime, with the machine to itself, is withdrawn on that evidence whatever any prediction says. A rung nobody has run is judged by the startup measurement like any other, because a measurement of one rung is not a prediction about the rest.
|
|
5
|
+
|
|
6
|
+
## 2.21.1
|
|
7
|
+
|
|
8
|
+
- **Fix**: A cost is learned only from an encoder that had the machine to itself. Beside another encoder a reading already contains that other work, and the budget then ADDS the same work again when it predicts — so the price of a file grew with every reading. Measured in the field 2026-08-15: copying, whose truth is 7.9x, was learned as 2.03x; decoding, whose calibration clips say 2.6x, as 0.87x. Every re-encoded rung was then refused (`not offering 720p=0.56x … 240p=0.66x`), the offer collapsed to the one copied height, and the viewer lost the quality menu entirely.
|
|
9
|
+
|
|
1
10
|
## 2.21.0
|
|
2
11
|
|
|
3
12
|
- **Fix**: The two costs added in 2.18.0 and 2.19.0 were never measured in production — both features were inert. The torrent's cost read `torrentPool.client`, a field that belongs to the pool implementation that no longer runs on this thread (the WebTorrent client lives on the worker), so the byte totals were always zero and the guard that needs two megabytes of movement never passed. The copy's cost sat in a branch its only caller had already filtered out, so it never ran. The totals now come from the worker over its own protocol, and the caller admits a copying session.
|
package/package.json
CHANGED
|
@@ -4750,6 +4750,9 @@ export class HlsSessionManager {
|
|
|
4750
4750
|
heights: ordered,
|
|
4751
4751
|
ownHeight: own,
|
|
4752
4752
|
playingHeight: playing,
|
|
4753
|
+
// What each rung was actually seen doing in this session, which is the
|
|
4754
|
+
// only thing a live reading may speak for.
|
|
4755
|
+
measuredHeights: this.#measuredRungSpeeds(owner),
|
|
4753
4756
|
// What the family is already spending while a rung is considered. The
|
|
4754
4757
|
// picture being COPIED is the common case and used to be priced at
|
|
4755
4758
|
// nothing; measured, it is about an eighth of the machine.
|
|
@@ -4759,7 +4762,18 @@ export class HlsSessionManager {
|
|
|
4759
4762
|
fps: Number(owner.outputFps) || TRANSCODE_FPS,
|
|
4760
4763
|
source: owner.sourceDecode ?? null,
|
|
4761
4764
|
transcodeVideo: owner.transcodeVideo === true,
|
|
4762
|
-
|
|
4765
|
+
// NOT the learned cost. What a rung is OFFERED on is the startup
|
|
4766
|
+
// measurement, which is taken on a quiet machine against known clips and
|
|
4767
|
+
// does not move; the figure learned from a live session moves with
|
|
4768
|
+
// whatever else the box was doing at that second, and three field
|
|
4769
|
+
// sessions in a row (2026-08-15) show what that costs: 0.87x, then
|
|
4770
|
+
// 1.34-1.57x against calibration's 2.6x, each reading refusing another
|
|
4771
|
+
// rung until the offer held one height and the menu disappeared with it.
|
|
4772
|
+
//
|
|
4773
|
+
// The learned figure keeps its job — but only over the rung it was
|
|
4774
|
+
// measured ON, and only to take that one away (below). A measurement of
|
|
4775
|
+
// one rung is not a prediction about the others.
|
|
4776
|
+
observedDecodeCostSec: null
|
|
4763
4777
|
});
|
|
4764
4778
|
if (owner !== session) {
|
|
4765
4779
|
// An orphan: its base is gone, so this is the family's last word and
|
|
@@ -4831,6 +4845,26 @@ export class HlsSessionManager {
|
|
|
4831
4845
|
*
|
|
4832
4846
|
* @param {HlsSession} session
|
|
4833
4847
|
*/
|
|
4848
|
+
/**
|
|
4849
|
+
* How many encoders are running and not suspended right now.
|
|
4850
|
+
*
|
|
4851
|
+
* A cost measured while two of them share the machine belongs to neither.
|
|
4852
|
+
*
|
|
4853
|
+
* @returns {number}
|
|
4854
|
+
*/
|
|
4855
|
+
#runningEncoders() {
|
|
4856
|
+
let running = 0;
|
|
4857
|
+
for (const session of this.sessionsById.values()) {
|
|
4858
|
+
if (session?.ffmpeg != null &&
|
|
4859
|
+
!hasChildExited(session.ffmpeg) &&
|
|
4860
|
+
session.encoderPaused !== true &&
|
|
4861
|
+
session.state !== "disposed") {
|
|
4862
|
+
running += 1;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
return running;
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4834
4868
|
async #learnFromEncoder(session) {
|
|
4835
4869
|
if (
|
|
4836
4870
|
!session ||
|
|
@@ -4866,6 +4900,20 @@ export class HlsSessionManager {
|
|
|
4866
4900
|
if (speed === null) {
|
|
4867
4901
|
return;
|
|
4868
4902
|
}
|
|
4903
|
+
// Only while this encoder had the machine to itself. A reading taken beside
|
|
4904
|
+
// another encoder already contains that other work — and the budget then
|
|
4905
|
+
// ADDS the same work again when it predicts, so the cost is counted twice
|
|
4906
|
+
// and grows with every reading. Measured 2026-08-15 in the field: copying,
|
|
4907
|
+
// whose truth is 7.9x, was learned as 2.03x, and decoding, whose clips say
|
|
4908
|
+
// 2.6x, as 0.87x. Every rung was then refused, the offer collapsed to the
|
|
4909
|
+
// one copied height, and the viewer lost the quality menu altogether.
|
|
4910
|
+
if (this.#runningEncoders() > 1) {
|
|
4911
|
+
return;
|
|
4912
|
+
}
|
|
4913
|
+
// What this rung did with the machine to itself — the one figure a live
|
|
4914
|
+
// reading is authority on, and what withdraws a rung that has been seen
|
|
4915
|
+
// failing without letting it speak for rungs nobody has run.
|
|
4916
|
+
session.lastAloneSpeed = speed;
|
|
4869
4917
|
if (speed < BUDGET_SPEED_OK && await this.#classifyTranscodeBound(session) === "download") {
|
|
4870
4918
|
return; // the torrent is what is short; this says nothing about the host
|
|
4871
4919
|
}
|
|
@@ -5128,6 +5176,32 @@ export class HlsSessionManager {
|
|
|
5128
5176
|
return cost;
|
|
5129
5177
|
}
|
|
5130
5178
|
|
|
5179
|
+
/**
|
|
5180
|
+
* The speed each rung of this family was last seen running at, when it was
|
|
5181
|
+
* running alone.
|
|
5182
|
+
*
|
|
5183
|
+
* A rung that has been watched failing is refused on that evidence; a rung
|
|
5184
|
+
* nobody has run says nothing about itself and is judged by the startup
|
|
5185
|
+
* measurement like any other.
|
|
5186
|
+
*
|
|
5187
|
+
* @param {HlsSession} base
|
|
5188
|
+
* @returns {Map<number, number>}
|
|
5189
|
+
*/
|
|
5190
|
+
#measuredRungSpeeds(base) {
|
|
5191
|
+
/** @type {Map<number, number>} */
|
|
5192
|
+
const speeds = new Map();
|
|
5193
|
+
for (const session of this.#familyOf(base)) {
|
|
5194
|
+
if (session.transcodeVideo !== true || !Number.isFinite(session.lastAloneSpeed)) {
|
|
5195
|
+
continue;
|
|
5196
|
+
}
|
|
5197
|
+
const height = this.variantHeightOf(session);
|
|
5198
|
+
if (height > 0) {
|
|
5199
|
+
speeds.set(height, session.lastAloneSpeed);
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
return speeds;
|
|
5203
|
+
}
|
|
5204
|
+
|
|
5131
5205
|
#sustainableHeights({
|
|
5132
5206
|
heights,
|
|
5133
5207
|
ownHeight,
|
|
@@ -5138,7 +5212,8 @@ export class HlsSessionManager {
|
|
|
5138
5212
|
source,
|
|
5139
5213
|
transcodeVideo,
|
|
5140
5214
|
observedDecodeCostSec = null,
|
|
5141
|
-
concurrentCostSec = 0
|
|
5215
|
+
concurrentCostSec = 0,
|
|
5216
|
+
measuredHeights = null
|
|
5142
5217
|
}) {
|
|
5143
5218
|
const benchmark = this.softwarePresetBenchmark;
|
|
5144
5219
|
if (!Array.isArray(benchmark) || benchmark.length === 0 || sourceHeight <= 0 || sourceWidth <= 0) {
|
|
@@ -5170,6 +5245,14 @@ export class HlsSessionManager {
|
|
|
5170
5245
|
kept.push(height);
|
|
5171
5246
|
continue;
|
|
5172
5247
|
}
|
|
5248
|
+
// A rung this session has actually been seen running below realtime is
|
|
5249
|
+
// withdrawn on that evidence, whatever the prediction says. This is the
|
|
5250
|
+
// one thing a live reading is authority on: itself.
|
|
5251
|
+
const measured = measuredHeights?.get(height) ?? null;
|
|
5252
|
+
if (measured !== null && measured < 1) {
|
|
5253
|
+
dropped.push(`${height}p=${measured.toFixed(2)}x measured`);
|
|
5254
|
+
continue;
|
|
5255
|
+
}
|
|
5173
5256
|
const width = Math.round(((sourceWidth / sourceHeight) * height) / 2) * 2;
|
|
5174
5257
|
const { speed, sustainable } = canSustainOutput({
|
|
5175
5258
|
benchmark,
|