@torrent-tv/proxy 2.107.39 → 2.107.41
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
|
@@ -629,7 +629,7 @@ export function wireOutputs({
|
|
|
629
629
|
viewerSecondsOn: (output, consumerId, now) => viewerSecondsOn(parts.viewers, output, consumerId, now),
|
|
630
630
|
minimumBufferSecondsFor: (output) => minimumBufferSecondsOf(output),
|
|
631
631
|
// What this proxy may keep of downloaded pieces on disk.
|
|
632
|
-
sourceStorageBytes: () => parts.machineBudget?.allowedFor
|
|
632
|
+
sourceStorageBytes: () => parts.machineBudget?.allowedFor("spilled pieces") ?? null,
|
|
633
633
|
getSourceStats: (...args) => parts.getSourceStats?.(...args) ?? null,
|
|
634
634
|
disposeSession: (...args) => parts.lifecycle.disposeSession(...args),
|
|
635
635
|
planEncodersSoon: (...args) => parts.encodeRuns.planEncodersSoon(...args),
|
package/services/storage/wire.js
CHANGED
|
@@ -194,6 +194,8 @@ export function wireMachineBudget({
|
|
|
194
194
|
|
|
195
195
|
return {
|
|
196
196
|
capacityOf: name => budget.capacityOf(name),
|
|
197
|
+
/** What one claimant was last allowed, or null before a measured division. */
|
|
198
|
+
allowedFor: name => budget.allowedFor(name),
|
|
197
199
|
/**
|
|
198
200
|
* @param {string | null} [resource] - One resource to revise; all when absent.
|
|
199
201
|
* @param {string | null} [because] - Who asked, for the revision's line.
|
|
@@ -687,8 +687,16 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
687
687
|
}
|
|
688
688
|
const tailStock = [];
|
|
689
689
|
if (trackState.some(({ tailEndSeconds }) => Number.isFinite(tailEndSeconds))) {
|
|
690
|
-
|
|
691
|
-
|
|
690
|
+
// Where each source's bytes stop being the near window's: the last byte
|
|
691
|
+
// its pieces read, in the file's own offsets.
|
|
692
|
+
const windowEnds = new Map();
|
|
693
|
+
for (const { sourceByteIntervals } of jobs) {
|
|
694
|
+
for (const [sourceId, ranges] of sourceByteIntervals) {
|
|
695
|
+
for (const range of ranges) windowEnds.set(sourceId, Math.max(windowEnds.get(sourceId) ?? 0, range.end));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const stop = scheduleTail({ trackState, sourceState, runStarts, trackFinish,
|
|
699
|
+
link, position, buffered, reserve, transferFinish, constrain, duration, stock: tailStock, windowEnds });
|
|
692
700
|
if (stop) {
|
|
693
701
|
return { ...result(false, null, buffered, reserve, null, stop.reason, preparedSegments),
|
|
694
702
|
...(stop.unavailableSource ? { unavailableSource: stop.unavailableSource } : {}) };
|
|
@@ -732,12 +740,13 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
732
740
|
// share (Home Assistant 2026-10-26, torrent-tv/meta#239). Said as such, and
|
|
733
741
|
// the viewer keeps waiting rather than starting into stops.
|
|
734
742
|
const storageBytes = Number(input.sourceStorageBytes);
|
|
735
|
-
|
|
736
|
-
|
|
743
|
+
const tailParts = tailStock.filter(part => !part.skipped);
|
|
744
|
+
if (Number.isFinite(delay) && Number.isFinite(storageBytes) && storageBytes >= 0 && tailParts.length > 0) {
|
|
745
|
+
const cushionBytes = tailParts.reduce((sum, part) => sum + Math.max(0, part.totalBytes - part.missingBytes) +
|
|
737
746
|
Math.max(0, part.missingBytes - part.bytesPerSecond * Math.max(0, part.finish - delay)), 0);
|
|
738
747
|
if (cushionBytes > storageBytes) {
|
|
739
748
|
return { ...result(false, null, buffered, reserve, null, "cushion-exceeds-storage", preparedSegments),
|
|
740
|
-
cushionBytes, storageBytes };
|
|
749
|
+
cushionBytes, storageBytes, tail: tailStock };
|
|
741
750
|
}
|
|
742
751
|
}
|
|
743
752
|
const originReady = trackState.every(({ clientRanges }) => compare(contiguousEnd(clientRanges, at), at) > 0);
|
|
@@ -748,6 +757,9 @@ export function predictPlaybackReadiness(input = {}) {
|
|
|
748
757
|
return { ...forecast, measuredAt: now, predictedStartAt: Number.isFinite(delay) ? now + delay * 1000 : null,
|
|
749
758
|
firstRequiredSegmentsSeconds,
|
|
750
759
|
firstRequiredStages,
|
|
760
|
+
// What the rest of the film weighs per source, at what rate it comes and
|
|
761
|
+
// when, or why a source has none: said in the readiness line.
|
|
762
|
+
tail: tailStock,
|
|
751
763
|
// The piece whose `kind` — its production, or its delivery to the page —
|
|
752
764
|
// comes `atSeconds` from now while the media before it lasts
|
|
753
765
|
// `aheadSeconds`: the difference is the delay.
|
|
@@ -786,14 +798,18 @@ function isPiece(segment) {
|
|
|
786
798
|
*
|
|
787
799
|
* @returns {{ reason: string, unavailableSource?: object } | null} Why there is no answer, or null.
|
|
788
800
|
*/
|
|
789
|
-
function scheduleTail({ trackState, sourceState,
|
|
790
|
-
position, buffered, reserve, transferFinish, constrain, duration, stock = null }) {
|
|
801
|
+
function scheduleTail({ trackState, sourceState, runStarts, trackFinish, link,
|
|
802
|
+
position, buffered, reserve, transferFinish, constrain, duration, stock = null, windowEnds = null }) {
|
|
791
803
|
const tailStart = Math.max(position, ...trackState.map(state => state.tailStartSeconds));
|
|
792
804
|
if (!Number.isFinite(tailStart)) return null;
|
|
793
805
|
const sourceDurations = new Map();
|
|
794
806
|
for (const state of trackState) {
|
|
795
807
|
const seconds = Math.max(0, state.tailEndSeconds - tailStart) * state.tailWorkFraction;
|
|
796
|
-
if (!(seconds > 0))
|
|
808
|
+
if (!(seconds > 0)) {
|
|
809
|
+
stock?.push({ track: state.track.id, skipped: "no-work", tailStart, tailEnd: state.tailEndSeconds,
|
|
810
|
+
workFraction: state.tailWorkFraction, tailSegments: state.tailSegmentCount });
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
797
813
|
for (const sourceId of new Set(state.track.sourceIds ?? [])) {
|
|
798
814
|
sourceDurations.set(sourceId, Math.max(sourceDurations.get(sourceId) ?? 0, seconds));
|
|
799
815
|
}
|
|
@@ -803,16 +819,23 @@ function scheduleTail({ trackState, sourceState, sourceByteCoverage, runStarts,
|
|
|
803
819
|
for (const [sourceId, seconds] of sourceDurations) {
|
|
804
820
|
const source = sourceState.get(sourceId);
|
|
805
821
|
if (!source) return { reason: "source-measurement-unavailable" };
|
|
806
|
-
if (source.source.complete === true)
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
822
|
+
if (source.source.complete === true) {
|
|
823
|
+
stock?.push({ sourceId, skipped: "complete" });
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
// Held means on this proxy now, past the last byte the near window's
|
|
827
|
+
// pieces read: those are counted above, and the window's planned arrivals
|
|
828
|
+
// counted again as held took 117 s of film off a 90-minute tail. The
|
|
829
|
+
// torrent thread's plan is not that end: it can reach the end of the file.
|
|
830
|
+
const windowEnd = windowEnds?.get(sourceId) ?? 0;
|
|
811
831
|
const bytes = estimateUnmappedSourceBytes(source.source, seconds, duration,
|
|
812
832
|
heldBeyond(source.source.residence, windowEnd), windowEnd);
|
|
813
833
|
if (bytes === null) return { reason: "source-measurement-unavailable",
|
|
814
834
|
unavailableSource: { sourceId, phase: "tail" } };
|
|
815
|
-
if (!(bytes > 0))
|
|
835
|
+
if (!(bytes > 0)) {
|
|
836
|
+
stock?.push({ sourceId, skipped: "held", seconds, windowEnd });
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
816
839
|
const { service } = source;
|
|
817
840
|
const rate = service.tailRate ?? service.rate;
|
|
818
841
|
if (!rate || !(rate.rateAt(0) > 0)) {
|
|
@@ -823,7 +846,7 @@ function scheduleTail({ trackState, sourceState, sourceByteCoverage, runStarts,
|
|
|
823
846
|
sourceArrivalAt = Math.max(sourceArrivalAt, service.finish);
|
|
824
847
|
// What this stretch weighs and what it still lacks: the stock the start
|
|
825
848
|
// asks to hold is read from these once the start's delay is known.
|
|
826
|
-
stock?.push({ totalBytes:
|
|
849
|
+
stock?.push({ sourceId, seconds, windowEnd, totalBytes:estimateUnmappedSourceBytes(source.source, seconds, duration, []) ?? bytes,
|
|
827
850
|
missingBytes: bytes, bytesPerSecond: rate.rateAt(0), finish: service.finish });
|
|
828
851
|
const state = trackState.find(candidate => candidate.track.sourceIds?.includes(sourceId));
|
|
829
852
|
const index = state?.segments.at(-1)?.index ?? 0;
|