@torrent-tv/proxy 2.93.1 → 2.93.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.93.1",
3
+ "version": "2.93.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": {
@@ -27,6 +27,14 @@ export class EncodeInputs {
27
27
 
28
28
  held() { return this.#held; }
29
29
  wanted() { return this.#held + [...this.#wanted.values()].reduce((sum, bytes) => sum + bytes, 0); }
30
+ required() {
31
+ let bytes = this.#held;
32
+ for (const [key, wanted] of this.#wanted) {
33
+ const request = this.#requests.get(key);
34
+ if (request && this.#urgent(request.output, request.from)) bytes += wanted;
35
+ }
36
+ return bytes;
37
+ }
30
38
  allow(bytes) {
31
39
  const changed = this.#allowed !== Math.max(0, bytes);
32
40
  this.#allowed = Math.max(0, bytes);
@@ -370,7 +370,11 @@ export class EncodeRuns {
370
370
  if (!whole) {
371
371
  this.#host.logger.warn(
372
372
  `encode: not publishing piece ${index} of ${address.slice(0, 60)}: ` +
373
- (reason ?? (throughSeconds === null ? "it holds no playable media" : `produced through ${throughSeconds}s, cut ${cut}s`))
373
+ (reason ?? (throughSeconds === null ? "it holds no playable media" : `produced through ${throughSeconds}s, cut ${cut}s`)) +
374
+ (interval ? ` expected=${interval.from}..${interval.to} tracks=${JSON.stringify(mediaRanges.tracks.map(track => ({
375
+ kind: track.kind, timescale: track.timescale, frame: track.productionFrame,
376
+ first: track.ranges[0], last: track.ranges.at(-1), count: track.ranges.length })),
377
+ (_key, value) => typeof value === "bigint" ? String(value) : value)}` : "")
374
378
  );
375
379
  return false;
376
380
  }
@@ -982,7 +982,8 @@ export function wireOutputs({
982
982
  index >= window.from && index <= window.to && window.withinSeconds === 0),
983
983
  changed: (output, result) => {
984
984
  if (result.kind === "terminal") {
985
- logger.warn(`encode input output=${output.outputKey} terminal=${result.reason}: ${result.message ?? ""}`);
985
+ logger.warn(`encode input output=${output.outputKey} terminal=${result.reason}: ${result.message ?? ""}` +
986
+ (Number.isFinite(result.bytes) ? ` bytes=${result.bytes} capacity=${result.capacity}` : ""));
986
987
  parts.serving.invalidateWaits(output);
987
988
  }
988
989
  parts.encodeRuns.planEncodersSoon();
@@ -90,7 +90,7 @@ export function wireMachineBudget({
90
90
 
91
91
  budget.defineResource({ name: "memory", readFree: () => availableMemoryBytes() });
92
92
  if (encodeInputs) budget.register({ name: "admitted encode inputs", resource: "memory",
93
- held: () => encodeInputs.held(), wanted: () => encodeInputs.wanted(), required: () => encodeInputs.held(), allow: bytes => encodeInputs.allow(bytes) });
93
+ held: () => encodeInputs.held(), wanted: () => encodeInputs.wanted(), required: () => encodeInputs.required(), allow: bytes => encodeInputs.allow(bytes) });
94
94
  if (indexMemory) budget.register({ name: "media metadata", resource: "memory",
95
95
  held: () => indexMemory.held(), wanted: () => indexMemory.wanted(), required: () => indexMemory.required(), allow: bytes => indexMemory.allow(bytes) });
96
96
 
@@ -27,7 +27,7 @@ export class DownloadMaps {
27
27
  if (!current || Object.keys(current).some(key => key !== "from" && key !== "to" && !Object.is(zone[key], key === "deadlineAt" ? deadlineAt : current[key]))) changed = true;
28
28
  return current ? [{ ...zone, ...current, from: zone.from, to: zone.to, deadlineAt }] : [];
29
29
  });
30
- file.zones = retained;
30
+ this.#setZones(file, retained);
31
31
  for (const [statement, metadata] of file.metadata) {
32
32
  if (metadata.interval && !this.wantsInterval(sourceKey, fileIndex, metadata.interval)) {
33
33
  file.metadata.delete(statement);
@@ -47,7 +47,7 @@ export class DownloadMaps {
47
47
  const resolved = await this.#resolvePlayback({ sourceKey, fileIndex, durationSeconds, zones: file.sourceZones,
48
48
  isCurrent: () => this.#files.get(`${sourceKey}:${fileIndex}`) === file && revision === file.revision });
49
49
  if (revision !== file.revision) return;
50
- file.zones = resolved;
50
+ this.#setZones(file, resolved);
51
51
  return this.#emit(file);
52
52
  }
53
53
 
@@ -87,10 +87,10 @@ export class DownloadMaps {
87
87
  const sources = [];
88
88
  for (const file of this.#files.values()) {
89
89
  if (file.sourceKey !== sourceKey) continue;
90
- const zones = file.zones.filter(zone => zone.outputKey === outputKey && zone.index === index);
91
- if (!zones.length) continue;
90
+ const ranges = file.inputs.get(outputKey)?.get(index);
91
+ if (!ranges?.length) continue;
92
92
  sources.push({ sourceId: `${sourceKey}:${file.fileIndex}`,
93
- ranges: zones.map(zone => ({ start: zone.byteStart, end: zone.byteEnd + 1 })) });
93
+ ranges });
94
94
  }
95
95
  return sources.length ? sources : null;
96
96
  }
@@ -130,7 +130,7 @@ export class DownloadMaps {
130
130
 
131
131
  forget(sourceKey, fileIndex, { keepPreparation = false } = {}) {
132
132
  const file = this.#file(sourceKey, fileIndex);
133
- file.zones = [];
133
+ this.#setZones(file, []);
134
134
  file.sourceZones = [];
135
135
  if (!keepPreparation) file.nativeZones = [];
136
136
  file.revision++;
@@ -158,12 +158,25 @@ export class DownloadMaps {
158
158
  const key = `${sourceKey}:${fileIndex}`;
159
159
  let file = this.#files.get(key);
160
160
  if (!file) {
161
- file = { sourceKey, fileIndex, durationSeconds: 0, zones: [], nativeZones: [], sourceZones: [], revision: 0, epoch: ++this.#nextEpoch, metadata: new Map(), pending: Promise.resolve() };
161
+ file = { sourceKey, fileIndex, durationSeconds: 0, zones: [], inputs: new Map(), nativeZones: [], sourceZones: [], revision: 0, epoch: ++this.#nextEpoch, metadata: new Map(), pending: Promise.resolve() };
162
162
  this.#files.set(key, file);
163
163
  }
164
164
  return file;
165
165
  }
166
166
 
167
+ #setZones(file, zones) {
168
+ file.zones = zones;
169
+ file.inputs = new Map();
170
+ for (const zone of zones) {
171
+ if (typeof zone.outputKey !== "string" || !Number.isSafeInteger(zone.index)) continue;
172
+ let output = file.inputs.get(zone.outputKey);
173
+ if (!output) file.inputs.set(zone.outputKey, output = new Map());
174
+ let ranges = output.get(zone.index);
175
+ if (!ranges) output.set(zone.index, ranges = []);
176
+ ranges.push({ start: zone.byteStart, end: zone.byteEnd + 1 });
177
+ }
178
+ }
179
+
167
180
  #emit(file) {
168
181
  const zones = [...file.zones, ...file.nativeZones];
169
182
  for (const { ranges, requestId, priority, urgent, deadlineAt, order } of file.metadata.values()) {