@torrent-tv/proxy 2.99.6 → 2.99.8

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.6",
3
+ "version": "2.99.8",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -653,16 +653,15 @@ export class MatroskaContainer extends Container {
653
653
  * One preceding cluster retains preroll; one following cluster covers demux
654
654
  * lookahead beyond the presentation cut. No packet payload is parsed here.
655
655
  */
656
- async supportsOriginalSourceRanges(interval = null) {
656
+ async supportsOriginalSourceRanges() {
657
657
  const cues = await this.readCues();
658
658
  if (!cues?.points.length) return false;
659
659
  const video = ContainerTrack.firstUsable(await this.readTracks(), "video");
660
- // Sound alone is read from its own packets, as an AVI's is
661
- // (`AviContainer`). From the original file it was given whole clusters,
662
- // picture included: 260 MB for 23 pieces of a soundtrack, 38 MB for one of
663
- // 171 KB, on Home Assistant (r44, r46, torrent-tv/meta#178). Asked of the
664
- // file as a whole, the answer is the picture's.
665
- if (video && interval?.trackIds?.length && !interval.trackIds.includes(video.trackNumber)) return false;
660
+ // A soundtrack alone is read from the original file too, picture clusters
661
+ // included (38 MB for one piece of 171 KB of sound, r46). Read from its own
662
+ // packets (proxy 2.99.6), the soundtrack's packet table was refused memory
663
+ // on Home Assistant, its first run was given one piece and never closed
664
+ // it, and no Matroska film started (r47, torrent-tv/meta#178).
666
665
  return cues.points.some(point => point.positions.some(position => !video || position.track === video.trackNumber));
667
666
  }
668
667
 
@@ -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`).