@torrent-tv/proxy 2.9.13 → 2.9.14
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/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/services/torrent-pool.js +60 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 2.9.14
|
|
2
|
+
|
|
3
|
+
- **New**: `GET /api/sources/:sourceKey/stats` now reports `headerBytes` / `headerDownloadedBytes` — how much of the file's header/index region (leading 256 KB + trailing 2 MB, the bytes the codec probe needs) is downloaded, counted by whole torrent pieces from the bitfield. Lets the browser show the download phase's progress and ETA toward the next (transcode) phase. Coarse by design (piece granularity).
|
|
4
|
+
|
|
1
5
|
## 2.9.13
|
|
2
6
|
|
|
3
7
|
- **Fix**: Video-copy path (`video=copy`, audio transcoded or copied) no longer drops video / desyncs audio at the start. The output timeline is now forced 0-based: the container `start_time` (parsed from the probe; many MKVs report ~0.1 s) is subtracted via `-output_ts_offset -start_time` together with `-copyts`, so segment 0 begins exactly at 0 with audio and video aligned (previously `-copyts` preserved the non-zero start, leaving a hole at the beginning where video was blank but audio played).
|
package/package.json
CHANGED
package/services/torrent-pool.js
CHANGED
|
@@ -15,6 +15,11 @@ import { logger } from "../utils/logger.js";
|
|
|
15
15
|
// small enough not to make "everything critical" (which defeats prioritization).
|
|
16
16
|
const PRIORITY_WINDOW_BYTES = 8 * 1024 * 1024;
|
|
17
17
|
|
|
18
|
+
// The file's header/index region the codec probe needs (phase 1). Must match
|
|
19
|
+
// the ranges prefetchFileEdges fetches: leading bytes + trailing bytes.
|
|
20
|
+
const HEADER_HEAD_BYTES = 256 * 1024;
|
|
21
|
+
const HEADER_TAIL_BYTES = 2 * 1024 * 1024;
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* Decode a raw torrent source value into the format expected by WebTorrent.
|
|
20
25
|
*
|
|
@@ -217,14 +222,68 @@ export class TorrentPool {
|
|
|
217
222
|
return { ...base, fileProgress: null, fileDownloaded: null, fileLength: null };
|
|
218
223
|
}
|
|
219
224
|
|
|
225
|
+
const header = this.#getHeaderRangeProgress(torrent, file);
|
|
226
|
+
|
|
220
227
|
return {
|
|
221
228
|
...base,
|
|
222
229
|
fileProgress: typeof file.progress === "number" ? file.progress : 0,
|
|
223
230
|
fileDownloaded: typeof file.downloaded === "number" ? file.downloaded : 0,
|
|
224
|
-
fileLength: typeof file.length === "number" ? file.length : 0
|
|
231
|
+
fileLength: typeof file.length === "number" ? file.length : 0,
|
|
232
|
+
// Phase-1 progress: how much of the header/index region (the bytes the
|
|
233
|
+
// codec probe needs before transcoding can start) is downloaded. Counted
|
|
234
|
+
// by whole pieces from the torrent bitfield, so it advances coarsely
|
|
235
|
+
// (piece granularity). Null when the bitfield/piece info is unavailable.
|
|
236
|
+
headerBytes: header ? header.totalBytes : null,
|
|
237
|
+
headerDownloadedBytes: header ? header.downloadedBytes : null
|
|
225
238
|
};
|
|
226
239
|
}
|
|
227
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Count, by whole torrent pieces, how many bytes of a file's header/index
|
|
243
|
+
* region (leading {@link HEADER_HEAD_BYTES} + trailing {@link HEADER_TAIL_BYTES})
|
|
244
|
+
* are downloaded. Used to show progress toward the codec-probe phase.
|
|
245
|
+
*
|
|
246
|
+
* @param {import("webtorrent").Torrent} torrent
|
|
247
|
+
* @param {import("webtorrent").TorrentFile} file
|
|
248
|
+
* @returns {{ totalBytes: number, downloadedBytes: number } | null}
|
|
249
|
+
*/
|
|
250
|
+
#getHeaderRangeProgress(torrent, file) {
|
|
251
|
+
const pieceLength = Number(torrent?.pieceLength);
|
|
252
|
+
const bitfield = torrent?.bitfield;
|
|
253
|
+
const fileLength = Number(file?.length);
|
|
254
|
+
if (
|
|
255
|
+
!Number.isFinite(pieceLength) || pieceLength <= 0 ||
|
|
256
|
+
!bitfield || typeof bitfield.get !== "function" ||
|
|
257
|
+
!Number.isFinite(fileLength) || fileLength <= 0
|
|
258
|
+
) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const fileOffset = Number.isFinite(file.offset) ? file.offset : 0;
|
|
262
|
+
const headEnd = Math.min(HEADER_HEAD_BYTES, fileLength) - 1;
|
|
263
|
+
const ranges = [[0, headEnd]];
|
|
264
|
+
const tailStart = Math.max(headEnd + 1, fileLength - HEADER_TAIL_BYTES);
|
|
265
|
+
if (tailStart <= fileLength - 1) {
|
|
266
|
+
ranges.push([tailStart, fileLength - 1]);
|
|
267
|
+
}
|
|
268
|
+
const pieces = new Set();
|
|
269
|
+
for (const [start, end] of ranges) {
|
|
270
|
+
const first = Math.floor((fileOffset + start) / pieceLength);
|
|
271
|
+
const last = Math.floor((fileOffset + end) / pieceLength);
|
|
272
|
+
for (let piece = first; piece <= last; piece += 1) {
|
|
273
|
+
pieces.add(piece);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
let totalBytes = 0;
|
|
277
|
+
let downloadedBytes = 0;
|
|
278
|
+
for (const piece of pieces) {
|
|
279
|
+
totalBytes += pieceLength;
|
|
280
|
+
if (bitfield.get(piece)) {
|
|
281
|
+
downloadedBytes += pieceLength;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { totalBytes, downloadedBytes };
|
|
285
|
+
}
|
|
286
|
+
|
|
228
287
|
/**
|
|
229
288
|
* Pre-fetch the leading and trailing bytes of a torrent file so that
|
|
230
289
|
* WebTorrent prioritises the pieces that contain file headers and footers.
|