@torrent-tv/proxy 2.107.45 → 2.107.46

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.45",
3
+ "version": "2.107.46",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -521,7 +521,7 @@ export function predictPlaybackReadiness(input = {}) {
521
521
  }));
522
522
  const sourceByteCoverage = new Map([...sourceByteService].map(([id, ranges]) => [id, numericCoverage(ranges)]));
523
523
  const sourceArrivals = new Map([...sourceByteService].map(([id, ranges]) => [id, indexedArrivals(ranges)]));
524
- let transferFinish = 0;
524
+ const transferJobs = [];
525
525
  const completions = [];
526
526
  const productions = [];
527
527
  for (const job of jobs) {
@@ -609,9 +609,17 @@ export function predictPlaybackReadiness(input = {}) {
609
609
  const sourcePlanned = sourceArrivalAt > 0 && plan.finish >= sourceArrivalAt;
610
610
  productions.push({ at: producedAt, trackIndex, segment, requiresSource: sourceArrivalAt > 0, sourceArrivalAt,
611
611
  sourcePlanned, runAdmissionAt, runOpenSeconds, productionStartAt, encodeWork });
612
- const deliveryAt = link.finish(sizeBits, Math.max(transferFinish, producedAt));
613
- transferFinish = deliveryAt;
614
- const appendSeconds = finiteNonNegative(track.appendSeconds);
612
+ transferJobs.push({ trackIndex, segment, sizeBits, readyAt: producedAt, sourceArrivalAt, sourcePlanned,
613
+ order: transferJobs.length, appendSeconds: finiteNonNegative(track.appendSeconds) });
614
+ }
615
+ // A transfer waits only for work that is ready on its own stream and for
616
+ // bytes already being served by the shared link. A video piece whose encode
617
+ // is still running cannot reserve link time ahead of a ready audio piece.
618
+ // Each stream remains ordered; the single link remains shared.
619
+ const transfers = scheduleTransfers(transferJobs, link);
620
+ const transferFinish = transfers.reduce((finish, transfer) => Math.max(finish, transfer.deliveryAt), 0);
621
+ for (const transfer of transfers) {
622
+ const { trackIndex, segment, deliveryAt, appendSeconds, sourceArrivalAt, sourcePlanned } = transfer;
615
623
  completions.push({ at: deliveryAt + appendSeconds, deliveryAt, appendSeconds, trackIndex, segment, sourceArrivalAt,
616
624
  sourcePlanned });
617
625
  }
@@ -780,6 +788,48 @@ function isPiece(segment) {
780
788
  Number.isFinite(segment?.endSeconds) && segment.endSeconds > segment.startSeconds;
781
789
  }
782
790
 
791
+ /**
792
+ * Serve each stream's fragments in order on one shared link, starting the
793
+ * earliest released fragment whenever the link becomes free.
794
+ *
795
+ * @param {Array<{ trackIndex: number, segment: object, sizeBits: number, readyAt: number,
796
+ * order: number, appendSeconds: number }>} jobs
797
+ * @param {{ finish: (bits: number, startAt: number) => number }} link
798
+ */
799
+ function scheduleTransfers(jobs, link) {
800
+ const queues = new Map();
801
+ for (const job of jobs) {
802
+ const queue = queues.get(job.trackIndex) ?? [];
803
+ queue.push(job);
804
+ queues.set(job.trackIndex, queue);
805
+ }
806
+ const served = new Map();
807
+ const trackFreeAt = new Map();
808
+ const transfers = [];
809
+ let linkFreeAt = 0;
810
+ while (transfers.length < jobs.length) {
811
+ let selected = null;
812
+ let selectedReadyAt = Number.POSITIVE_INFINITY;
813
+ for (const [trackIndex, queue] of queues) {
814
+ const nextIndex = served.get(trackIndex) ?? 0;
815
+ const job = queue[nextIndex];
816
+ if (!job) continue;
817
+ const readyAt = Math.max(job.readyAt, trackFreeAt.get(trackIndex) ?? 0);
818
+ if (readyAt < selectedReadyAt || readyAt === selectedReadyAt && (selected === null || job.order < selected.order)) {
819
+ selected = job;
820
+ selectedReadyAt = readyAt;
821
+ }
822
+ }
823
+ if (!selected) break;
824
+ const deliveryAt = link.finish(selected.sizeBits, Math.max(linkFreeAt, selectedReadyAt));
825
+ transfers.push({ ...selected, deliveryAt });
826
+ served.set(selected.trackIndex, (served.get(selected.trackIndex) ?? 0) + 1);
827
+ trackFreeAt.set(selected.trackIndex, deliveryAt);
828
+ linkFreeAt = deliveryAt;
829
+ }
830
+ return transfers;
831
+ }
832
+
783
833
  /**
784
834
  * A duration-level estimate of the suffix after the exact near window. Source
785
835
  * work uses each file's aggregate byte density and measured download service;