@torrent-tv/proxy 2.107.41 → 2.107.42

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.107.41",
3
+ "version": "2.107.42",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -687,16 +687,8 @@ export function predictPlaybackReadiness(input = {}) {
687
687
  }
688
688
  const tailStock = [];
689
689
  if (trackState.some(({ tailEndSeconds }) => Number.isFinite(tailEndSeconds))) {
690
- // Where each source's bytes stop being the near window's: the last byte
691
- // its pieces read, in the file's own offsets.
692
- const windowEnds = new Map();
693
- for (const { sourceByteIntervals } of jobs) {
694
- for (const [sourceId, ranges] of sourceByteIntervals) {
695
- for (const range of ranges) windowEnds.set(sourceId, Math.max(windowEnds.get(sourceId) ?? 0, range.end));
696
- }
697
- }
698
690
  const stop = scheduleTail({ trackState, sourceState, runStarts, trackFinish,
699
- link, position, buffered, reserve, transferFinish, constrain, duration, stock: tailStock, windowEnds });
691
+ link, position, buffered, reserve, transferFinish, constrain, duration, stock: tailStock });
700
692
  if (stop) {
701
693
  return { ...result(false, null, buffered, reserve, null, stop.reason, preparedSegments),
702
694
  ...(stop.unavailableSource ? { unavailableSource: stop.unavailableSource } : {}) };
@@ -799,7 +791,7 @@ function isPiece(segment) {
799
791
  * @returns {{ reason: string, unavailableSource?: object } | null} Why there is no answer, or null.
800
792
  */
801
793
  function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
802
- position, buffered, reserve, transferFinish, constrain, duration, stock = null, windowEnds = null }) {
794
+ position, buffered, reserve, transferFinish, constrain, duration, stock = null }) {
803
795
  const tailStart = Math.max(position, ...trackState.map(state => state.tailStartSeconds));
804
796
  if (!Number.isFinite(tailStart)) return null;
805
797
  const sourceDurations = new Map();
@@ -823,11 +815,17 @@ function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
823
815
  stock?.push({ sourceId, skipped: "complete" });
824
816
  continue;
825
817
  }
826
- // Held means on this proxy now, past the last byte the near window's
827
- // pieces read: those are counted above, and the window's planned arrivals
828
- // counted again as held took 117 s of film off a 90-minute tail. The
829
- // torrent thread's plan is not that end: it can reach the end of the file.
830
- const windowEnd = windowEnds?.get(sourceId) ?? 0;
818
+ // Held means on this proxy now, in the bytes of the film after the near
819
+ // window: the window's own bytes are counted above, and its planned
820
+ // arrivals counted again as held took 117 s of film off a 90-minute tail.
821
+ // Where those bytes begin is placed by the density the tail is priced at.
822
+ // Neither the torrent thread's plan nor the bytes the window reads are
823
+ // that place: both can reach the end of the file — a copied Matroska file
824
+ // reads its index there — and the whole of Despicable Me 3 then counted
825
+ // as held (Home Assistant 2026-10-26, torrent-tv/meta#239).
826
+ const fileLength = Number(source.source.fileLength);
827
+ const windowEnd = fileLength > 0 && duration > 0
828
+ ? Math.min(fileLength, Math.max(0, fileLength * tailStart / duration)) : 0;
831
829
  const bytes = estimateUnmappedSourceBytes(source.source, seconds, duration,
832
830
  heldBeyond(source.source.residence, windowEnd), windowEnd);
833
831
  if (bytes === null) return { reason: "source-measurement-unavailable",