@torrent-tv/proxy 2.107.23 → 2.107.24

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.23",
3
+ "version": "2.107.24",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
package/server.js CHANGED
@@ -73,7 +73,6 @@ import { nativeSourceMap, nativeOriginalSourceMap } from "./services/viewer/Nati
73
73
  import { MediaReadRequests } from "./services/media/MediaReadRequests.js";
74
74
  import { SegmentInputs } from "./services/media/SegmentInputs.js";
75
75
  import { directSegmentInput } from "./services/media/direct-segment-input.js";
76
- import { piecesRunPastEnd } from "./services/encode/output/Timeline.js";
77
76
  import { ContainerTrack } from "./services/media/tracks/ContainerTrack.js";
78
77
  import { pauseCoefficient, viewerStartsOn } from "./services/viewer/PriorityMap.js";
79
78
  import { coalescing } from "./utils/coalesce.js";
@@ -717,9 +716,9 @@ export async function startProxyServer({
717
716
  const revision = outputParts.segmentStore.revisionOf(output.outputKey);
718
717
  const links = consumers.map(viewer => viewer.linkReading()?.linkMbps)
719
718
  .filter(value => Number.isFinite(value) && value > 0);
720
- // A piece copying a stream answers for the second its end falls in,
721
- // as the encode plan reads it (`piecesRunPastEnd`).
722
- played.push({ outputKey: output.outputKey, grid, runsPastEnd: piecesRunPastEnd(output.spec),
719
+ // Every piece answers for its exact ending boundary, as the encode
720
+ // plan reads it: the player may request the preceding piece there.
721
+ played.push({ outputKey: output.outputKey, grid,
723
722
  encodeSpeed: outputParts.encodeCost.latestSpeedReadingOf(output)?.speed,
724
723
  bytesPerSecond: bytesPerSecondOf(output, revision), linkMbps: links.length ? Math.min(...links) : null,
725
724
  readyRevision: revision, ready: () => outputParts.segmentStore.sizesOf(output.outputKey),
@@ -26,7 +26,6 @@ import { cutOf, cutShiftOf, judgeNeighbors, judgePiece, leadingSecondsOf, lacksO
26
26
  import { leadingPictures, presentationSegment } from "./segment-formats/presentation-segment.js";
27
27
  import { InputFailures, failedAdmittedInput } from "./InputFailures.js";
28
28
  import { readProcessCpuSeconds } from "./host-load.js";
29
- import { piecesRunPastEnd } from "./output/Timeline.js";
30
29
 
31
30
 
32
31
  /**
@@ -616,13 +615,11 @@ export class EncodeRuns {
616
615
  // each: 454 pieces against 401 on the field file.
617
616
  for (const [address, sessions] of byOutput) {
618
617
  const timeline = sessions[0].timeline;
619
- const spec = sessions[0].spec;
620
618
  this.#host.encodeOrchestrator.notePriorityMap(
621
619
  address,
622
620
  timeline?.inSegments?.(
623
621
  this.#host.priority.mapForOutput(address),
624
- Number(timeline?.segmentCount) || 0,
625
- { runsPastEnd: piecesRunPastEnd(spec) }
622
+ Number(timeline?.segmentCount) || 0
626
623
  ) ?? []
627
624
  );
628
625
  }
@@ -42,32 +42,6 @@
42
42
  * reveal where the file's cuts really are.
43
43
  */
44
44
 
45
- /**
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.
54
- *
55
- * Field 2026-10-09, MP3 copied out of an AVI: a seek to 4000 s asked for the
56
- * piece ending at 4000 s. Field r43 (Home Assistant, 2026-10-15), a copied
57
- * Matroska picture: a seek to 1000.000 s, the published start of #96, asked for
58
- * #95 on both tracks; the picture's pieces were not counted as copies, nothing
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).
63
- *
64
- * @param {{ video?: object | null, audio?: object | null, transcodesVideo?: boolean, transcodesAudio?: boolean } | null | undefined} spec
65
- * @returns {boolean}
66
- */
67
- export function piecesRunPastEnd(spec) {
68
- return (Boolean(spec?.video) && spec.transcodesVideo !== true) || Boolean(spec?.audio);
69
- }
70
-
71
45
  export class Timeline {
72
46
  /**
73
47
  * @param {object} params
@@ -185,13 +159,10 @@ export class Timeline {
185
159
  * @param {import("../../viewer/PriorityMap.js").PriorityMap} map - What every
186
160
  * viewer of this output wants, in seconds of film.
187
161
  * @param {number} segmentCount - How many pieces this output has.
188
- * @param {{ runsPastEnd?: boolean }} [shape] - `runsPastEnd`: the output's
189
- * pieces copy a stream, which is cut where its packets end, so a piece
190
- * holds sound past its published end by up to one packet.
191
162
  * @returns {import("../../viewer/PriorityMap.js").DemandZone[]} Runs of pieces
192
163
  * that agree. Empty where nothing is stated, which says nobody is coming.
193
164
  */
194
- inSegments(map, segmentCount, { runsPastEnd = false } = {}) {
165
+ inSegments(map, segmentCount) {
195
166
  if (!(segmentCount > 0) || !map || !(map.durationSeconds > 0)) {
196
167
  return [];
197
168
  }
@@ -202,17 +173,16 @@ export class Timeline {
202
173
  // whole, so it is wanted as soon as the soonest second inside it is
203
174
  // wanted — and the piece a viewer is standing in the middle of is wanted
204
175
  // exactly as much as the second under their feet.
205
- // A COPIED PIECE ALSO HOLDS THE SECOND ITS END FALLS IN. A copy ends at
206
- // the first packet boundary at or after its published end, and the
207
- // player places the next piece where this one truly ends (hls.js
208
- // `updateFragPTSDTS` carries a parsed piece's end to every later one).
209
- // Field 2026-10-09, MP3 copied out of an AVI on a 4 s grid: pieces ended
210
- // up to 24 ms late, and a seek to 4000 s asked for the piece ending at
211
- // 4000 s, which nothing made because second 4000 belonged to the next.
176
+ // A PIECE ALSO HOLDS THE SECOND ITS END FALLS IN. At an exact playlist
177
+ // boundary hls.js may request the fragment ending there before it asks
178
+ // for the one beginning there. Excluding that second from this piece can
179
+ // leave the request waiting while the plan starts at the following one.
180
+ // Copied streams also run past their published end by up to one packet,
181
+ // and hls.js carries that parsed end to every later piece.
212
182
  const from = Math.max(0, Math.floor(this.publishedStartOf(index)));
213
183
  const end = this.publishedStartOf(index + 1);
214
184
  const until = index + 1 < segmentCount
215
- ? Math.max(from + 1, runsPastEnd ? Math.floor(end) + 1 : Math.ceil(end))
185
+ ? Math.max(from + 1, Math.floor(end) + 1)
216
186
  : map.durationSeconds;
217
187
  // Asked of the map for the piece's seconds as a whole, not second by
218
188
  // second: a walk of every second of the film for every output on every
@@ -273,10 +273,9 @@ class Segment {
273
273
  this.from = from;
274
274
  this.to = to;
275
275
  /**
276
- * Up to which second the map's zones are read for it: its end, and for a
277
- * piece that copies a stream the second its end falls in as well, as the
278
- * encode plan reads it (`Timeline.inSegments`): the player asks a viewer
279
- * standing at its end for this piece, and its bytes are fetched for them.
276
+ * Up to which second the map's zones are read for it: its end and the exact
277
+ * end boundary. The encode plan includes that boundary for every output,
278
+ * since the player may request the piece ending there before the next one.
280
279
  */
281
280
  this.reach = reach;
282
281
  /** Whether a zone of the map overlaps it. */
@@ -311,16 +310,15 @@ class Segment {
311
310
 
312
311
  /** One output: its grid, how long its segments take to make and deliver, what is made, and its next input. */
313
312
  class OutputState {
314
- constructor(outputKey, grid, runsPastEnd = false) {
313
+ constructor(outputKey, grid) {
315
314
  this.outputKey = outputKey;
316
315
  this.grid = grid;
317
- this.runsPastEnd = runsPastEnd;
318
316
  /** @type {Segment[]} */
319
317
  this.segments = [];
320
318
  for (let index = 0; index + 1 < grid.length; index++) {
321
319
  const end = grid[index + 1];
322
320
  this.segments.push(new Segment(this, index, grid[index], end,
323
- runsPastEnd && index + 2 < grid.length ? Math.max(end, Math.floor(end) + 1) : end));
321
+ index + 2 < grid.length ? Math.max(end, Math.floor(end) + 1) : end));
324
322
  }
325
323
  this.encodeSpeed = undefined;
326
324
  this.bytesPerSecond = undefined;
@@ -380,7 +378,7 @@ export class PlaybackProjection {
380
378
  * @param {object} params
381
379
  * @param {Array<{ from: number, to: number, priority: number, urgent?: boolean, behind?: boolean, deferred?: boolean,
382
380
  * deadlineAt?: number }>} params.zones - The file's map zones, deadlines as instants.
383
- * @param {Array<{ outputKey: string, grid: number[], runsPastEnd?: boolean, encodeSpeed?: number, bytesPerSecond?: number | null,
381
+ * @param {Array<{ outputKey: string, grid: number[], encodeSpeed?: number, bytesPerSecond?: number | null,
384
382
  * linkMbps?: number | null, readyRevision: unknown, ready: () => Map<number, number>,
385
383
  * target: number | null }>} params.outputs - Every output of the file somebody is watching, with
386
384
  * what decides how long its segments take to make and deliver (`sourceDeadline`), what it has made
@@ -401,13 +399,13 @@ export class PlaybackProjection {
401
399
  for (const info of outputs) {
402
400
  seen.add(info.outputKey);
403
401
  let output = this.#outputs.get(info.outputKey);
404
- if (output && (output.grid !== info.grid || output.runsPastEnd !== Boolean(info.runsPastEnd))) {
402
+ if (output && output.grid !== info.grid) {
405
403
  this.#dropOutput(output);
406
404
  output = undefined;
407
405
  }
408
406
  const fresh = !output;
409
407
  if (fresh) {
410
- output = new OutputState(info.outputKey, info.grid, Boolean(info.runsPastEnd));
408
+ output = new OutputState(info.outputKey, info.grid);
411
409
  this.#outputs.set(info.outputKey, output);
412
410
  }
413
411
  // How long a segment of it takes to make and deliver.
@@ -436,8 +434,9 @@ export class PlaybackProjection {
436
434
  for (const segment of output.segments) if (this.#demand(segment, zones, ordered, nextBase, leadMoved)) reworked++;
437
435
  } else {
438
436
  for (const { from, to } of stretches) {
439
- // A piece reaching past its end is read for the stretch after it too.
440
- for (let index = Math.max(0, segmentAt(output.grid, from) - (output.runsPastEnd ? 1 : 0));
437
+ // A viewer at an exact playlist boundary may request the piece
438
+ // ending there before requesting the one that starts there.
439
+ for (let index = Math.max(0, segmentAt(output.grid, from) - 1);
441
440
  index < output.segments.length && output.segments[index].from < to; index++) {
442
441
  if (this.#demand(output.segments[index], zones, ordered, nextBase, false)) reworked++;
443
442
  }