@torrent-tv/proxy 2.99.1 → 2.99.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.99.1",
3
+ "version": "2.99.3",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -43,24 +43,29 @@
43
43
  */
44
44
 
45
45
  /**
46
- * Whether an output's pieces copy a stream, picture or sound, and so may hold
47
- * a little past their published end: a copy ends at the first packet boundary
48
- * at or after it, and the player places the next piece where this one truly
49
- * ends (hls.js `updateFragPTSDTS`). A viewer standing at a published edge is
50
- * then asked for by the piece BEFORE it.
46
+ * Whether an output's pieces may hold a little past their published end: a
47
+ * piece ends at the first frame boundary at or after it, and the player places
48
+ * the next piece where this one truly ends (hls.js `updateFragPTSDTS`). A
49
+ * viewer standing at a published edge is then asked for by the piece BEFORE
50
+ * it. True of a copied picture, whose pieces end at a packet, and of sound
51
+ * however it is made: copied sound ends at a packet, and encoded sound at a
52
+ * frame of the encoder — 1024 samples of AAC, 21.3 ms at 48 kHz. Only a
53
+ * re-encoded picture, given a keyframe at every cut, ends where it is published.
51
54
  *
52
55
  * Field 2026-10-09, MP3 copied out of an AVI: a seek to 4000 s asked for the
53
56
  * piece ending at 4000 s. Field r43 (Home Assistant, 2026-10-15), a copied
54
57
  * Matroska picture: a seek to 1000.000 s, the published start of #96, asked for
55
58
  * #95 on both tracks; the picture's pieces were not counted as copies, nothing
56
- * made #95, and the picture stood 131 s until the viewer moved
57
- * (torrent-tv/meta#178).
59
+ * made #95, and the picture stood 131 s until the viewer moved. With the
60
+ * picture counted (r44), its #95 was made in 6.4 s and the soundtrack's, AAC
61
+ * encoded from the film (`its muxer counts cuts -0.021333s`), was still not
62
+ * made (torrent-tv/meta#178).
58
63
  *
59
64
  * @param {{ video?: object | null, audio?: object | null, transcodesVideo?: boolean, transcodesAudio?: boolean } | null | undefined} spec
60
65
  * @returns {boolean}
61
66
  */
62
67
  export function piecesRunPastEnd(spec) {
63
- return (Boolean(spec?.video) && spec.transcodesVideo !== true) || (Boolean(spec?.audio) && spec.transcodesAudio !== true);
68
+ return (Boolean(spec?.video) && spec.transcodesVideo !== true) || Boolean(spec?.audio);
64
69
  }
65
70
 
66
71
  export class Timeline {
@@ -496,7 +496,11 @@ export class QualityController {
496
496
  bufferedSec: buffer?.bufferedSec ?? 0,
497
497
  slope: buffer?.slope ?? null,
498
498
  reportGapSec: buffer?.reportGapSec ?? 0,
499
- secondsToReady: this.#secondsToReady(session)
499
+ secondsToReady: this.#secondsToReady(session),
500
+ // What is left of the film after them: a buffer holding all of it drains
501
+ // because the film ends.
502
+ remainingSec: Number.isFinite(buffer?.positionSec) && session.file?.durationSeconds > 0
503
+ ? Math.max(0, session.file.durationSeconds - buffer.positionSec) : null
500
504
  });
501
505
  if (!threat) {
502
506
  return false;
@@ -20,6 +20,13 @@
20
20
  *
21
21
  * A time to ready that is not known is not assumed to be short: with nothing to
22
22
  * show there is time left, a draining buffer is a threat.
23
+ *
24
+ * A BUFFER HOLDING THE REST OF THE FILM IS NO THREAT: it drains because the
25
+ * film ends, and no output can add to it. Field r43 (Home Assistant,
26
+ * 2026-10-15): the last two minutes of an AVI were read as a draining buffer,
27
+ * a faster setting was prepared 53 times and given up each time with nothing
28
+ * left to give, and the player was asked for 240p eight times
29
+ * (torrent-tv/meta#178).
23
30
  */
24
31
 
25
32
  /**
@@ -31,14 +38,19 @@
31
38
  * two reports.
32
39
  * @param {number | null} params.secondsToReady - How long the new output would
33
40
  * take to close the piece the viewer needs; null when not known.
41
+ * @param {number | null} [params.remainingSec] - The film left after where the
42
+ * viewer stands; null when not known.
34
43
  * @returns {{ threat: boolean, secondsToEmpty: number | null }}
35
44
  */
36
- export function drainThreat({ bufferedSec, slope, reportGapSec, secondsToReady }) {
45
+ export function drainThreat({ bufferedSec, slope, reportGapSec, secondsToReady, remainingSec = null }) {
37
46
  if (!Number.isFinite(slope) || slope >= 0) {
38
47
  return { threat: false, secondsToEmpty: null };
39
48
  }
40
49
  const held = Number.isFinite(bufferedSec) && bufferedSec > 0 ? bufferedSec : 0;
41
50
  const secondsToEmpty = held / -slope;
51
+ if (Number.isFinite(remainingSec) && held >= remainingSec) {
52
+ return { threat: false, secondsToEmpty };
53
+ }
42
54
  if (!Number.isFinite(secondsToReady) || secondsToReady < 0) {
43
55
  return { threat: true, secondsToEmpty };
44
56
  }
@@ -338,7 +338,8 @@ export function bufferOf(viewers, output, consumerId, spanSec) {
338
338
  }
339
339
  const trend = viewer.bufferTrend(spanSec);
340
340
  const held = Number.isFinite(viewer.bufferedSeconds) ? Math.max(0, viewer.bufferedSeconds) : 0;
341
- return { bufferedSec: held, slope: trend?.slope ?? null, reportGapSec: trend?.reportGapSec ?? 0 };
341
+ return { bufferedSec: held, slope: trend?.slope ?? null, reportGapSec: trend?.reportGapSec ?? 0,
342
+ positionSec: viewer.positionSeconds() ?? null };
342
343
  }
343
344
 
344
345
  /**