@torrent-tv/proxy 2.99.7 → 2.99.9

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.99.7",
3
+ "version": "2.99.9",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -94,19 +94,20 @@ export async function futureDownload(entries, { now = Date.now(), findStore = pi
94
94
  continue;
95
95
  }
96
96
  const remaining = piece?.missing;
97
- const speed = (torrent.wires ?? []).reduce((sum, wire) => {
97
+ const holders = [];
98
+ for (const wire of torrent.wires ?? []) {
98
99
  // Only a peer that can be asked for it now: one choking us is asked for
99
100
  // nothing outside the pieces it allows, whatever it carried before.
100
- if (wire.destroyed || !wire.peerPieces?.get(index) || !mayAsk(wire, index)) return sum;
101
+ if (wire.destroyed || !wire.peerPieces?.get(index) || !mayAsk(wire, index)) continue;
101
102
  // What it carried with its queue kept full, where it has been seen so:
102
103
  // its speed while our selection gave it little measures our asking
103
104
  // (`SelectionLimit`, torrent-tv/meta#178).
104
105
  const rate = Number(limit?.capacityOf?.(wire) ?? wire.downloadSpeed?.());
105
- return Number.isFinite(rate) && rate > 0 ? sum + rate : sum;
106
- }, 0);
107
- pending.push({ arrivals, index, ...band, seconds:
108
- Number.isSafeInteger(remaining) && remaining > 0 && remaining <= piece.length && speed > 0
109
- ? remaining / speed : null });
106
+ if (Number.isFinite(rate) && rate > 0) holders.push({ wire, rate });
107
+ }
108
+ pending.push({ arrivals, index, ...band, holders, remaining:
109
+ Number.isSafeInteger(remaining) && remaining > 0 && remaining <= piece.length && holders.length > 0
110
+ ? remaining : null });
110
111
  if (++steps % 256 === 0) {
111
112
  await nextTurn();
112
113
  if (!isCurrent()) return new Map();
@@ -120,15 +121,62 @@ export async function futureDownload(entries, { now = Date.now(), findStore = pi
120
121
  // before it have. Charged the whole band's end instead, a band of many
121
122
  // segments — the map states one per stretch of a level since 2.97.27 — would
122
123
  // put its nearest piece at its farthest piece's time (torrent-tv/meta#178).
124
+ //
125
+ // AND IN PARALLEL WHERE THE PEERS DIFFER. A piece is brought by the peers
126
+ // holding it, each from when it has brought what was asked of it before, and
127
+ // no sooner than the measured supply of every peer that holds a piece still
128
+ // to come carries all the bytes ahead of it. Summed one piece after another
129
+ // at its own holders' speed, a two-hour film whose far pieces each had a few
130
+ // unchoked holders was forecast to arrive in 127 838 s at 2.33 MB/s, and a
131
+ // seek was held for 39 s on it (r49, torrent-tv/meta#178). One holder for
132
+ // every piece gives the sum it was; a piece nobody can be asked for still
133
+ // leaves every piece after it unknown.
123
134
  pending.sort((left, right) => compareBands(left, right) || left.index - right.index);
124
- let finish = now;
125
- for (const { arrivals, index, seconds } of pending) {
126
- finish = finish === null || seconds === null ? null : finish + seconds * 1000;
127
- if (finish !== null) arrivals.set(index, finish);
135
+ const supply = new Map();
136
+ for (const { holders } of pending) for (const { wire, rate } of holders) supply.set(wire, rate);
137
+ const total = [...supply.values()].reduce((sum, rate) => sum + rate, 0);
138
+ const freeAt = new Map();
139
+ let ahead = 0;
140
+ let known = true;
141
+ for (const { arrivals, index, holders, remaining } of pending) {
142
+ if (!known || remaining === null || !(total > 0)) {
143
+ known = false;
144
+ continue;
145
+ }
146
+ ahead += remaining;
147
+ const finish = Math.max(sharedFinish(holders, freeAt, remaining, now), now + ahead / total * 1000);
148
+ for (const { wire } of holders) freeAt.set(wire, Math.max(freeAt.get(wire) ?? now, finish));
149
+ arrivals.set(index, finish);
128
150
  }
129
151
  return isCurrent() ? forecasts : new Map();
130
152
  }
131
153
 
154
+ /**
155
+ * When peers bringing `bytes` together finish: each starts when it is free
156
+ * and brings at its own rate, so the finish is where their combined bytes
157
+ * reach `bytes` (the earliest of them working alone at first, more joining as
158
+ * they come free).
159
+ *
160
+ * @param {Array<{ wire: object, rate: number }>} holders - Bytes per second.
161
+ * @param {Map<object, number>} freeAt - Milliseconds.
162
+ * @param {number} bytes
163
+ * @param {number} now - Milliseconds.
164
+ * @returns {number} Milliseconds.
165
+ */
166
+ function sharedFinish(holders, freeAt, bytes, now) {
167
+ const starts = holders.map(({ wire, rate }) => ({ at: Math.max(now, freeAt.get(wire) ?? now), rate }))
168
+ .sort((left, right) => left.at - right.at);
169
+ let rate = 0;
170
+ let weighted = 0;
171
+ for (let index = 0; index < starts.length; index++) {
172
+ rate += starts[index].rate;
173
+ weighted += starts[index].rate * starts[index].at;
174
+ const finish = (bytes * 1000 + weighted) / rate;
175
+ if (index === starts.length - 1 || finish <= starts[index + 1].at) return finish;
176
+ }
177
+ return now;
178
+ }
179
+
132
180
  /**
133
181
  * Whether this peer can be asked for this piece now: it is not choking us, or
134
182
  * it allows this piece while it does (BEP 6, WebTorrent 2.8.5 `_updateWire`).
@@ -313,6 +313,14 @@ export function createTunnelClient({
313
313
  signalRoutes.delete(sessionId);
314
314
  }
315
315
  }
316
+ // Closed because this proxy is stopping (`disconnect`), which clears the
317
+ // connection before its close arrives. Read as a handover, a stopping
318
+ // proxy said a replacement carried its tunnel while viewers found no
319
+ // proxy at all (r48, torrent-tv/meta#178).
320
+ if (stopped) {
321
+ log(`Tunnel closed (code=${event.code}): this proxy is stopping.`);
322
+ return;
323
+ }
316
324
  // A connection this one replaced. The server closes it as soon as the
317
325
  // replacement registers, which is the whole point of renewing early —
318
326
  // there is nothing to report and nothing to reconnect, because the tunnel