@torrent-tv/proxy 2.15.2 → 2.15.3

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 CHANGED
@@ -1,3 +1,7 @@
1
+ ## 2.15.3
2
+
3
+ - **Fix**: A magnet whose swarm never answered no longer poisons the film for good. It leaves a torrent with the right infohash and no file list, and WebTorrent then refuses the same film opened from a `.torrent` as a duplicate — so the answer to every later attempt came from the entry that knows nothing: `Proxy playback plan request failed (404): File index was not found in torrent`, reproduced in a browser 2026-08-15, and no reload could clear it because the useless entry outlives them all. A source that carries the metadata now replaces one that lacks it.
4
+
1
5
  ## 2.15.2
2
6
 
3
7
  - **Fix**: A segment request that can never be answered is answered as absent instead of being held for a minute. Changing audio track makes hls.js ask the new stream for segment #0 before anything else; the run was at #354, the repair reaches sixty segments back and no further, no seek was coming, and an encoder only moves forward — so the request was unanswerable from the moment it arrived, and holding it simply spent the player's own patience. Measured 2026-08-15: the track was made ready in 7.1 s at the viewer's position, and the viewer then watched a spinner for **63 s** — sixty of them the hold, the rest the player recovering after it failed. Deliberately narrower than the refusal 2.14.1 shipped and 2.14.2 withdrew: a request within the repair's reach, or one with a seek on its way, is still held, because for those the encoder is about to be moved there.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.15.2",
3
+ "version": "2.15.3",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -438,6 +438,17 @@ export function fileDownloadedBytes(torrent, file) {
438
438
  * File-level piece selection is tracked through a reference-count map so
439
439
  * that only files with at least one active stream cause downloading.
440
440
  */
441
+ /**
442
+ * Whether what we are about to add is a magnet — a name for content, with no
443
+ * file list in it — rather than a .torrent, which carries one.
444
+ *
445
+ * @param {string | Buffer} torrentId
446
+ * @returns {boolean}
447
+ */
448
+ function isMagnetSource(torrentId) {
449
+ return typeof torrentId === "string" && /^magnet:\?/i.test(torrentId.trim());
450
+ }
451
+
441
452
  export class TorrentPool {
442
453
  /**
443
454
  * In-flight `client.add()` promises keyed by the same key as `torrents`.
@@ -989,6 +1000,25 @@ export class TorrentPool {
989
1000
  const dupMatch = /duplicate torrent ([0-9a-f]{40})/i.exec(message);
990
1001
  if (dupMatch) {
991
1002
  const existing = this.client.torrents.find((t) => t?.infoHash === dupMatch[1]);
1003
+ // A torrent already here but WITHOUT metadata is not an answer to a
1004
+ // request that carries metadata. A magnet whose swarm never answered
1005
+ // leaves exactly that: an entry with the right infohash, no file
1006
+ // list, and no way to get one. The same film opened from a .torrent
1007
+ // then collides with it and is refused as a duplicate — measured
1008
+ // 2026-08-15 in a browser, "File index was not found in torrent", and
1009
+ // no reload could ever fix it because the poisoned entry outlives
1010
+ // every attempt. The one that knows the files wins.
1011
+ if (existing && !existing.ready && !isMagnetSource(torrentId)) {
1012
+ logger.info(
1013
+ `torrent-pool: replacing ${dupMatch[1].slice(0, 8)} — it was added from a magnet that never ` +
1014
+ "found peers, and this source carries the metadata it lacks"
1015
+ );
1016
+ existing.destroy({ destroyStore: false }, () => {
1017
+ this.#pending.delete(key);
1018
+ resolve(this.getTorrent(sourceType, source));
1019
+ });
1020
+ return;
1021
+ }
992
1022
  if (existing) {
993
1023
  const settle = () => {
994
1024
  this.torrents.set(key, existing);