@torrent-tv/proxy 2.100.4 → 2.100.6

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.100.4",
3
+ "version": "2.100.6",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -32,18 +32,28 @@ import { logger } from "../../utils/logger.js";
32
32
  export const CONTAINER_HEAD_BYTES = 256 * 1024;
33
33
 
34
34
  /**
35
- * The size of the file and its OpenSubtitles hash, from the two edges. A read
36
- * that has not arrived throws `BytesUnavailable`, so nothing partial is kept.
35
+ * The size of the file and its OpenSubtitles hash, from the two edges. Nothing
36
+ * partial is kept: an edge that has not arrived is named instead.
37
+ *
38
+ * Both edges are read at once and both missing ones named in one answer. Read
39
+ * one after the other, a cold start asked the swarm for the file's last piece
40
+ * only once its first had arrived: on Home Assistant the last piece, which
41
+ * also holds a Matroska file's Cues, came 2.4 s after the first and the start
42
+ * waited for it (r66, torrent-tv/meta#178).
37
43
  *
38
44
  * @param {{ readRange: (start: number, end: number) => Promise<Buffer | null>, fileSize: number }} params
39
- * @returns {Promise<{ hash: string, size: number } | null>} `null` for a file too short to have one.
45
+ * @returns {Promise<{ hash: string, size: number } | { missing: Array<[number, number]> } | null>}
46
+ * `null` for a file too short to have one.
40
47
  */
41
48
  async function readFingerprint({ readRange, fileSize }) {
42
49
  if (!hasOshash(fileSize)) return null;
43
50
  const read = strictReader(readRange, fileSize);
44
- const head = await read(0, OSHASH_EDGE_BYTES - 1);
45
- const tail = await read(fileSize - OSHASH_EDGE_BYTES, fileSize - 1);
46
- return { hash: oshash(fileSize, head, tail), size: fileSize };
51
+ const reads = await Promise.allSettled([read(0, OSHASH_EDGE_BYTES - 1), read(fileSize - OSHASH_EDGE_BYTES, fileSize - 1)]);
52
+ const failed = reads.find(reading => reading.status === "rejected" && !isUnavailable(reading.reason));
53
+ if (failed) throw failed.reason;
54
+ const missing = reads.filter(reading => reading.status === "rejected").map(reading => [reading.reason.start, reading.reason.end]);
55
+ if (missing.length) return { missing };
56
+ return { hash: oshash(fileSize, reads[0].value, reads[1].value), size: fileSize };
47
57
  }
48
58
 
49
59
  /**
@@ -181,6 +191,10 @@ export class ContainerOrchestrator {
181
191
  if (statement === "fingerprint") {
182
192
  // A fact about the bytes at the two ends of the file, whatever the format.
183
193
  const value = await readFingerprint(params);
194
+ if (value?.missing) {
195
+ logger.info(`media request=${requestId} needs bytes=${value.missing.map(([start, end]) => `${start}-${end}`).join(",")}`);
196
+ return { kind: "needs-ranges", ranges: value.missing, requestId };
197
+ }
184
198
  return value ? { kind: "result", value, requestId } : { kind: "terminal", reason: "file-too-short", requestId };
185
199
  }
186
200
  const container = await this.containerFor(params);
@@ -973,9 +973,15 @@ export class MatroskaContainer extends Container {
973
973
  Math.max(track.seekPrerollSeconds ?? 0, track.defaultDurationSeconds ?? 0)));
974
974
  let preceding = -1;
975
975
  for (let index = 0; index < points.length && points[index].seconds <= interval.from - preroll; index++) preceding = index;
976
- // The preceding cue's Cluster also retains audio that overlaps this cut
977
- // and decoder dependencies before the first presented packet.
978
- return preceding < 0 ? first : points[Math.max(0, preceding - 1)].at;
976
+ // A picture's leading frames may depend on the Cluster before its cue, so
977
+ // a picture begins one cue earlier. Sound and subtitles do not: the
978
+ // preroll above already reaches the cue whose Cluster holds a frame that
979
+ // overlaps the cut and the frames a decoder needs before it. Begun a cue
980
+ // earlier as well, a soundtrack's walk started a Cluster before the bytes
981
+ // the picture's input had copied for the same piece and could not be
982
+ // walked from them (r65, torrent-tv/meta#178).
983
+ const back = requested.some(track => track.type === "video") ? 1 : 0;
984
+ return preceding < 0 ? first : points[Math.max(0, preceding - back)].at;
979
985
  }
980
986
 
981
987
  /**