@torrent-tv/proxy 2.99.20 → 2.99.21

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.20",
3
+ "version": "2.99.21",
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
@@ -868,7 +868,7 @@ export async function startProxyServer({
868
868
  },
869
869
  // `timing`, when given, is told how long each read of a file waited behind
870
870
  // the reads asked before it and how long it took (torrent-tv/meta#166).
871
- resolveEncodeInput: async (output, fromIndex, toIndex, timing = null) => {
871
+ resolveEncodeInput: async (output, fromIndex, toIndex, timing = null, isCurrent = null) => {
872
872
  const grid = output.timeline?.published ?? output.timeline?.boundaries;
873
873
  const from = grid?.[fromIndex], to = grid?.[toIndex + 1];
874
874
  if (!Number.isFinite(from) || !(to > from)) return { kind: "terminal", reason: "output-interval-not-declared" };
@@ -914,6 +914,7 @@ export async function startProxyServer({
914
914
  if (media.kind !== "result") return media;
915
915
  const timeShiftSeconds = Number(media.value?.startTimeSeconds) || 0;
916
916
  const sourceInterval = output.timeline.sourceInterval(fromIndex, toIndex, timeShiftSeconds);
917
+ if (isCurrent) params.isCurrent = isCurrent;
917
918
  params.packetInterval = { ...sourceInterval,
918
919
  trackIds: wanted.map(track => track.trackNumber),
919
920
  modes: Object.fromEntries(wanted.map(track => [track.trackNumber, modes.get(track)])) };
@@ -92,10 +92,10 @@ export class EncodeInputs {
92
92
  }
93
93
 
94
94
  /** The container's statement of an interval, its time added to the request's account. */
95
- async #resolveTimed(timing, output, from, to) {
95
+ async #resolveTimed(timing, output, from, to, isCurrent = null) {
96
96
  const startedAt = this.#now();
97
97
  try {
98
- return await this.#resolve(output, from, to, timing);
98
+ return await this.#resolve(output, from, to, timing, isCurrent);
99
99
  } finally {
100
100
  timing.resolutions += 1;
101
101
  timing.resolveMs += this.#now() - startedAt;
@@ -384,7 +384,11 @@ export class EncodeInputs {
384
384
  reads: 0, queuedMs: 0, readMs: 0, heldMs: 0, budgetMs: 0 };
385
385
  request.timing.attempts += 1;
386
386
  request.promise = (async () => {
387
- const resolved = await this.#resolveTimed(request.timing, request.output, request.from, request.from);
387
+ // A request the plan withdraws stops its reads: a soundtrack piece's
388
+ // packet walk, left to run, held the file's reads and the piece the
389
+ // viewer had seeked to waited 16.9 s behind it (r59, torrent-tv/meta#178).
390
+ const resolved = await this.#resolveTimed(request.timing, request.output, request.from, request.from,
391
+ () => this.#requests.get(request.key) === request);
388
392
  if (resolved.kind === "result") this.#original.set(request.output, resolved.sources?.every(source => source.input.original === true) === true);
389
393
  if (this.#requests.get(request.key) !== request) return;
390
394
  let result;
@@ -215,7 +215,8 @@ export class ContainerOrchestrator {
215
215
  value = await container.readSourceRanges(params.packetInterval);
216
216
  if (value.kind !== "result") return { ...value, requestId };
217
217
  } else if (statement === "packets") {
218
- value = await container.readPacketIndex(params.packetInterval);
218
+ value = await container.readPacketIndex(params.packetInterval, { isCurrent: params.isCurrent });
219
+ if (value === null) return { kind: "terminal", reason: "request-obsolete", requestId };
219
220
  await value?.prepareAudioDependencies?.(params.packetInterval, container.readRange);
220
221
  } else {
221
222
  throw new TypeError(`Unknown media statement: ${statement}`);
@@ -782,7 +782,12 @@ export class MatroskaContainer extends Container {
782
782
  streamSearchSeconds: FFMPEG_STREAM_SEARCH_SECONDS };
783
783
  }
784
784
 
785
- async readPacketIndex(interval) {
785
+ /**
786
+ * @param {object} [interval]
787
+ * @param {{ isCurrent?: () => boolean }} [options] - A walk whose request is
788
+ * no longer current stops between clusters and answers null.
789
+ */
790
+ async readPacketIndex(interval, { isCurrent } = {}) {
786
791
  if (this.#packets) return this.#packets;
787
792
  const layout = await this.#segmentLayout();
788
793
  if (!layout) throw new Error("Matroska Segment is absent.");
@@ -801,7 +806,8 @@ export class MatroskaContainer extends Container {
801
806
  const complete = new Set();
802
807
  let whole = true;
803
808
  for (const track of requested) {
804
- const chain = await this.#readTrackPackets(layout, tracks, track, start, interval);
809
+ const chain = await this.#readTrackPackets(layout, tracks, track, start, interval, isCurrent);
810
+ if (!chain) return null;
805
811
  streams.set(track.trackNumber, chain.records);
806
812
  if (chain.reachesEnd) complete.add(track.trackNumber);
807
813
  if (!chain.reachesEnd || chain.startAt !== layout.firstClusterAt) whole = false;
@@ -825,7 +831,7 @@ export class MatroskaContainer extends Container {
825
831
  *
826
832
  * @returns {Promise<{ records: PacketRecordsChain, startAt: number, reachesEnd: boolean }>}
827
833
  */
828
- async #readTrackPackets(layout, tracks, track, start, interval) {
834
+ async #readTrackPackets(layout, tracks, track, start, interval, isCurrent) {
829
835
  const id = track.trackNumber;
830
836
  const previous = this.#packetWalks.get(id) ?? Promise.resolve();
831
837
  let finished;
@@ -882,7 +888,8 @@ export class MatroskaContainer extends Container {
882
888
  const following = readings.find(reading => reading.startAt > current.startAt);
883
889
  const outcome = await walkMatroskaPackets({ readRange: this.readRange, fileSize: this.fileSize,
884
890
  portionBytes: this.portionBytes, layout, tracks, trackIds: [id], interval, packetMemory: this.packetMemory,
885
- state: current, until: following?.startAt ?? null, isCovered: covered, readsThrough });
891
+ state: current, until: following?.startAt ?? null, isCovered: covered, readsThrough, isCurrent });
892
+ if (outcome === "obsolete") return null;
886
893
  if (outcome !== "until") break;
887
894
  current.next = following;
888
895
  }
@@ -44,9 +44,12 @@ function recordedTracks(tracks, trackIds) {
44
44
  * `start` on, where the caller knows it: those are read a portion at a time
45
45
  * across clusters too.
46
46
  *
47
- * @returns {Promise<"covered" | "until" | "end">}
47
+ * A walk whose `isCurrent` turns false stops at the next cluster, where its
48
+ * state is whole, and answers "obsolete".
49
+ *
50
+ * @returns {Promise<"covered" | "until" | "end" | "obsolete">}
48
51
  */
49
- export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, layout, tracks, trackIds, interval, packetMemory, state, until = null, isCovered = null, readsThrough = null }) {
52
+ export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, layout, tracks, trackIds, interval, packetMemory, state, until = null, isCovered = null, readsThrough = null, isCurrent = null }) {
50
53
  // INSIDE A CLUSTER EVERY BLOCK HEADER IS READ, so its bytes are read a
51
54
  // portion at a time up to the cluster's end, and never past it. Read a
52
55
  // header at a time, each a read of a few bytes from the torrent thread, the
@@ -71,6 +74,7 @@ export async function walkMatroskaPackets({ readRange, fileSize, portionBytes, l
71
74
  if (covered()) return "covered";
72
75
  let at = state.at;
73
76
  while (at < limit) {
77
+ if (!state.cluster && isCurrent?.() === false) return "obsolete";
74
78
  const element = state.cluster?.element ?? await validHeader(reader, at, layout.segmentEnd);
75
79
  if (element.id !== CLUSTER) {
76
80
  if (element.end === null) throw new Error("An unknown-sized Matroska element is not a Cluster.");