@torrent-tv/proxy 2.95.7 → 2.95.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.95.7",
3
+ "version": "2.95.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": {
@@ -1826,17 +1826,19 @@ export class TorrentPool {
1826
1826
 
1827
1827
  /**
1828
1828
  * Schedule removal of a torrent (with its on-disk store) after
1829
- * {@link TORRENT_IDLE_TTL_MS} of nothing being wanted of it. Idempotent —
1830
- * replaces any existing timer for the torrent.
1829
+ * {@link TORRENT_IDLE_TTL_MS} of nothing being wanted of it. The clock runs
1830
+ * from when it stopped being wanted, so a timer already running is kept:
1831
+ * replacing it on every report of the same state extended the time without
1832
+ * end, and the withdrawal of one map's 4900 zones replaced it 3524 times in
1833
+ * four seconds (Home Assistant 2026-10-07, torrent-tv/meta#95).
1831
1834
  *
1832
1835
  * @param {import("webtorrent").Torrent} torrent
1833
1836
  * @returns {void}
1834
1837
  */
1835
1838
  #scheduleIdleRemoval(torrent) {
1836
- if (!torrent) {
1839
+ if (!torrent || this.#idleTimers.has(torrent)) {
1837
1840
  return;
1838
1841
  }
1839
- this.#cancelIdleRemoval(torrent);
1840
1842
  const name = typeof torrent.name === "string" ? torrent.name : "(unknown)";
1841
1843
  const infoHashShort = String(torrent.infoHash ?? "?").slice(0, 8);
1842
1844
  logger.info(`torrent-pool: scheduling idle removal for "${name}" [${infoHashShort}] in ${TORRENT_IDLE_TTL_MS / 1000}s`);
@@ -19,13 +19,25 @@ export class DownloadMaps {
19
19
  file.sourceZones = zones.map(zone => Number.isFinite(zone.deadlineAt) || !Number.isFinite(zone.withinSeconds)
20
20
  ? zone : { ...zone, deadlineAt: now + Math.max(0, zone.withinSeconds) * 1000 });
21
21
  let changed = false;
22
+ // Until the new zones are resolved to bytes, a resolved zone stays while
23
+ // the film it covers is still wanted. A viewer moving on re-cuts every zone
24
+ // without making its film unwanted; dropping the zones that lost their
25
+ // exact interval published an empty map every few seconds of playback,
26
+ // which let the swarm go and emptied the encoder's resolved input (Home
27
+ // Assistant 2026-10-07, torrent-tv/meta#95). A zone no new zone overlaps —
28
+ // a seek away — is withdrawn at once.
22
29
  const retained = file.zones.flatMap(zone => {
23
30
  const interval = zone.downloadInterval ?? zone;
24
31
  const current = file.sourceZones.find(candidate => candidate.from === interval.from && candidate.to === interval.to);
25
- const deadlineAt = current && Number.isFinite(current.deadlineAt)
26
- ? current.deadlineAt + (Math.max(0, zone.from - current.from) - (zone.leadSeconds ?? 0)) * 1000 : current?.deadlineAt;
27
- if (!current || Object.keys(current).some(key => key !== "from" && key !== "to" && !Object.is(zone[key], key === "deadlineAt" ? deadlineAt : current[key]))) changed = true;
28
- return current ? [{ ...zone, ...current, from: zone.from, to: zone.to, deadlineAt }] : [];
32
+ if (!current) {
33
+ const stillWanted = file.sourceZones.some(candidate => candidate.from < interval.to && interval.from < candidate.to);
34
+ if (!stillWanted) changed = true;
35
+ return stillWanted ? [zone] : [];
36
+ }
37
+ const deadlineAt = Number.isFinite(current.deadlineAt)
38
+ ? current.deadlineAt + (Math.max(0, zone.from - current.from) - (zone.leadSeconds ?? 0)) * 1000 : current.deadlineAt;
39
+ if (Object.keys(current).some(key => key !== "from" && key !== "to" && !Object.is(zone[key], key === "deadlineAt" ? deadlineAt : current[key]))) changed = true;
40
+ return [{ ...zone, ...current, from: zone.from, to: zone.to, deadlineAt }];
29
41
  });
30
42
  this.#setZones(file, retained);
31
43
  for (const [statement, metadata] of file.metadata) {