@torrent-tv/proxy 2.107.53 → 2.107.55
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 +1 -1
- package/routes/api/sources/stats/get.js +4 -4
- package/routes/api/transcode-sessions/net-report/post.js +18 -0
- package/routes/api/transcode-sessions/progress/get.js +9 -1
- package/services/encode/EncodeRun.js +13 -3
- package/services/encode/RunProgress.js +11 -2
- package/services/encode/SpeedReadings.js +10 -2
- package/services/server/ViewerRequests.js +172 -44
- package/services/torrent/worker/worker.js +27 -3
- package/services/viewer/OutputForecastGeometry.js +19 -1
- package/services/viewer/Viewer.js +117 -17
- package/services/viewer/ViewerPlaybackState.js +25 -7
- package/services/viewer/playback-readiness.js +253 -32
|
@@ -42,6 +42,9 @@ import { startMemoryReport, WORKER_MEMORY_SAMPLE_MS } from "../../storage/memory
|
|
|
42
42
|
import { profileThisThread } from "../../storage/cpu-profile.js";
|
|
43
43
|
import { forwardLogsTo, logger } from "../../../utils/logger.js";
|
|
44
44
|
|
|
45
|
+
const WORKER_CLOCK_EPOCH = performance.timeOrigin;
|
|
46
|
+
let sourceSnapshotRevision = 0;
|
|
47
|
+
|
|
45
48
|
/**
|
|
46
49
|
* Everything this thread logs goes to the main thread, which owns the file.
|
|
47
50
|
*
|
|
@@ -492,6 +495,8 @@ async function runCommand(command, params, id) {
|
|
|
492
495
|
|
|
493
496
|
case Command.FILE_STATS: {
|
|
494
497
|
const statsStartedAt = performance.now();
|
|
498
|
+
const sourceCaptureStartedAt = statsStartedAt;
|
|
499
|
+
const sourceRevision = ++sourceSnapshotRevision;
|
|
495
500
|
emitWaitTrace(params.sourceKey, "W10", "begin", { startedAt: statsStartedAt,
|
|
496
501
|
fileIndex: params.fileIndex, forecast: params.forecast === true });
|
|
497
502
|
const finishStatsTrace = (outcome, fields = {}) => {
|
|
@@ -504,7 +509,14 @@ async function runCommand(command, params, id) {
|
|
|
504
509
|
if (!torrent) {
|
|
505
510
|
const whole = wholeSources.fileOf(params.sourceKey, params.fileIndex);
|
|
506
511
|
finishStatsTrace(whole ? "whole-file" : "pending");
|
|
507
|
-
return
|
|
512
|
+
return {
|
|
513
|
+
...(whole ? wholeFileStats(whole.length) : { pending: true }),
|
|
514
|
+
sourceCapture: {
|
|
515
|
+
owner: "torrent-worker", clock: "performance", clockEpoch: WORKER_CLOCK_EPOCH,
|
|
516
|
+
startedAt: sourceCaptureStartedAt, completedAt: performance.now(), revision: sourceRevision,
|
|
517
|
+
validity: whole ? "valid" : "unknown", unknownReason: whole ? null : "source-not-ready"
|
|
518
|
+
}
|
|
519
|
+
};
|
|
508
520
|
}
|
|
509
521
|
// What this file's own interruptions demand, measured by the reader in
|
|
510
522
|
// this thread. It travels with the stats because the caller asking for
|
|
@@ -515,7 +527,8 @@ async function runCommand(command, params, id) {
|
|
|
515
527
|
const file = Array.isArray(torrent?.files) ? torrent.files[params.fileIndex] : null;
|
|
516
528
|
const forecastAt = performance.now();
|
|
517
529
|
const forecast = file && params.forecast === true ? downloadForecastOf(torrent) : null;
|
|
518
|
-
const
|
|
530
|
+
const forecastCompletedAt = performance.now();
|
|
531
|
+
const forecastMs = params.forecast === true ? forecastCompletedAt - forecastAt : 0;
|
|
519
532
|
const stats = pool.getFileStats(torrent, params.fileIndex);
|
|
520
533
|
const ranges = [];
|
|
521
534
|
if (forecast) for (const [piece, availableAt] of forecast.arrivals) {
|
|
@@ -526,8 +539,19 @@ async function runCommand(command, params, id) {
|
|
|
526
539
|
finishStatsTrace("ok", { forecast: params.forecast === true });
|
|
527
540
|
return {
|
|
528
541
|
...stats,
|
|
542
|
+
sourceCapture: {
|
|
543
|
+
owner: "torrent-worker", clock: "performance", clockEpoch: WORKER_CLOCK_EPOCH,
|
|
544
|
+
startedAt: sourceCaptureStartedAt, completedAt: performance.now(), revision: sourceRevision,
|
|
545
|
+
validity: stats ? "valid" : "unknown", unknownReason: stats ? null : "source-stats-unavailable"
|
|
546
|
+
},
|
|
529
547
|
...(params.forecast === true ? { downloadForecast: {
|
|
530
|
-
measuredAt: forecast?.measuredAt ?? Date.now(), global: true, ranges
|
|
548
|
+
measuredAt: forecast?.measuredAt ?? Date.now(), global: true, ranges,
|
|
549
|
+
capture: {
|
|
550
|
+
owner: "torrent-worker", clock: "performance", clockEpoch: WORKER_CLOCK_EPOCH,
|
|
551
|
+
startedAt: forecastAt, completedAt: forecastCompletedAt, revision: sourceRevision,
|
|
552
|
+
validity: forecast ? "valid" : "unknown",
|
|
553
|
+
unknownReason: forecast ? null : "source-arrival-forecast-unavailable"
|
|
554
|
+
}
|
|
531
555
|
} } : {}),
|
|
532
556
|
supply: supplyFiguresFor(torrent?.infoHash, file?.name, params.segmentSeconds ?? 4),
|
|
533
557
|
// How long this answer waited for the arrival forecast, which the
|
|
@@ -27,10 +27,11 @@ export class OutputForecastGeometry {
|
|
|
27
27
|
const count = timeline?.segmentCount ?? 0;
|
|
28
28
|
let forecast = this.#byOutput.get(output);
|
|
29
29
|
if (!forecast) {
|
|
30
|
-
forecast = { pieces: [], geometry: {} };
|
|
30
|
+
forecast = { pieces: [], tailPieces: [], tailSegments: [], geometry: {} };
|
|
31
31
|
this.#byOutput.set(output, forecast);
|
|
32
32
|
}
|
|
33
33
|
const kept = forecast.pieces;
|
|
34
|
+
const keptTail = forecast.tailPieces;
|
|
34
35
|
const segments = [];
|
|
35
36
|
const lookahead = Number(this.#host.lookaheadSeconds);
|
|
36
37
|
const start = Number.isFinite(positionSeconds) ? Math.max(0, positionSeconds) : 0;
|
|
@@ -50,6 +51,23 @@ export class OutputForecastGeometry {
|
|
|
50
51
|
else high = middle;
|
|
51
52
|
}
|
|
52
53
|
const last = low;
|
|
54
|
+
const tailSegments = [];
|
|
55
|
+
for (let index = last; index < count; index += 1) {
|
|
56
|
+
const startSeconds = timeline.publishedStartOf(index);
|
|
57
|
+
const endSeconds = timeline.publishedStartOf(index + 1);
|
|
58
|
+
const sourceInputs = this.#host.sourceInputsFor?.(output, index) ?? undefined;
|
|
59
|
+
const previous = keptTail[index];
|
|
60
|
+
if (previous && previous.startSeconds === startSeconds && previous.endSeconds === endSeconds &&
|
|
61
|
+
sameInputs(previous.sourceInputs, sourceInputs)) {
|
|
62
|
+
tailSegments.push(previous);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const segment = { index, startSeconds, endSeconds, sourceInputs };
|
|
66
|
+
keptTail[index] = segment;
|
|
67
|
+
tailSegments.push(segment);
|
|
68
|
+
}
|
|
69
|
+
if (keptTail.length > count) keptTail.length = count;
|
|
70
|
+
forecast.tailSegments = tailSegments;
|
|
53
71
|
const indexes = new Set([0]);
|
|
54
72
|
for (let index = first; index < last; index++) indexes.add(index);
|
|
55
73
|
for (const index of indexes) {
|
|
@@ -53,6 +53,34 @@ function fragmentEventAges(value) {
|
|
|
53
53
|
Number.isFinite(value?.[name]) && value[name] >= 0 ? value[name] : null]));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function browserCaptureOf(report, sessionId) {
|
|
57
|
+
const clockEpoch = report.captureClockEpoch;
|
|
58
|
+
const startedAt = report.captureStartedAtBrowser;
|
|
59
|
+
const completedAt = report.captureCompletedAtBrowser;
|
|
60
|
+
const valid = typeof report.captureEpoch === "string" && report.captureEpoch.length > 0 &&
|
|
61
|
+
Number.isFinite(report.captureEpochStartedAtBrowser) && Number.isFinite(report.captureRevision) &&
|
|
62
|
+
Number.isSafeInteger(report.captureRevision) && report.captureRevision > 0 &&
|
|
63
|
+
Number.isFinite(clockEpoch) && Number.isFinite(startedAt) && Number.isFinite(completedAt) &&
|
|
64
|
+
report.captureEpochStartedAtBrowser >= clockEpoch && startedAt >= report.captureEpochStartedAtBrowser &&
|
|
65
|
+
completedAt >= startedAt &&
|
|
66
|
+
Number.isSafeInteger(report.generation) && report.generation >= 0;
|
|
67
|
+
return {
|
|
68
|
+
owner: "browser",
|
|
69
|
+
clock: "performance",
|
|
70
|
+
clockEpoch: Number.isFinite(clockEpoch) ? clockEpoch : null,
|
|
71
|
+
startedAt: Number.isFinite(startedAt) ? startedAt : null,
|
|
72
|
+
completedAt: Number.isFinite(completedAt) ? completedAt : null,
|
|
73
|
+
actionId: typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null,
|
|
74
|
+
generation: Number.isSafeInteger(report.generation) ? report.generation : null,
|
|
75
|
+
revision: Number.isSafeInteger(report.captureRevision) ? report.captureRevision : null,
|
|
76
|
+
epoch: typeof report.captureEpoch === "string" ? report.captureEpoch : null,
|
|
77
|
+
epochStartedAt: Number.isFinite(report.captureEpochStartedAtBrowser)
|
|
78
|
+
? report.captureEpochStartedAtBrowser : null,
|
|
79
|
+
validity: valid ? "valid" : "unknown",
|
|
80
|
+
unknownReason: valid ? null : "missing-or-invalid-browser-capture"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
56
84
|
function appendServiceSnapshot(value) {
|
|
57
85
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
58
86
|
const snapshot = {};
|
|
@@ -522,6 +550,18 @@ export class Viewer {
|
|
|
522
550
|
appendEpoch = null,
|
|
523
551
|
appendEpochStartedAt = null,
|
|
524
552
|
appendSnapshotAt = null,
|
|
553
|
+
captureClockEpoch = null,
|
|
554
|
+
captureEpoch = null,
|
|
555
|
+
captureEpochStartedAtBrowser = null,
|
|
556
|
+
captureRevision = null,
|
|
557
|
+
captureStartedAtBrowser = null,
|
|
558
|
+
captureCompletedAtBrowser = null,
|
|
559
|
+
linkSampleStartedAtBrowser = null,
|
|
560
|
+
linkSampleCompletedAtBrowser = null,
|
|
561
|
+
linkSampleActionId = null,
|
|
562
|
+
linkSampleGeneration = null,
|
|
563
|
+
linkSampleClockEpoch = null,
|
|
564
|
+
linkSampleRevision = null,
|
|
525
565
|
appendService: appendServiceReading = null,
|
|
526
566
|
fragmentService: fragmentServiceReading = null,
|
|
527
567
|
bufferedAheadSec,
|
|
@@ -564,6 +604,18 @@ export class Viewer {
|
|
|
564
604
|
const sampleMbps = Number(linkSampleMbps);
|
|
565
605
|
const sampleAt = Number(linkSampleAt);
|
|
566
606
|
const previous = this.netReport;
|
|
607
|
+
const browserCapture = browserCaptureOf({ captureClockEpoch, captureEpoch, captureEpochStartedAtBrowser,
|
|
608
|
+
captureRevision, captureStartedAtBrowser, captureCompletedAtBrowser, generation }, appendSessionId);
|
|
609
|
+
const previousCapture = previous?.bufferCapture ?? null;
|
|
610
|
+
const captureOrderKnown = browserCapture.clockEpoch !== null && browserCapture.epoch !== null &&
|
|
611
|
+
browserCapture.epochStartedAt !== null && browserCapture.revision !== null && browserCapture.revision > 0;
|
|
612
|
+
const sameCaptureClock = captureOrderKnown && browserCapture.clockEpoch === previousCapture?.clockEpoch;
|
|
613
|
+
const sameCaptureEpoch = sameCaptureClock && browserCapture.epoch === previousCapture?.epoch;
|
|
614
|
+
const newerCapture = captureOrderKnown && (!previousCapture || !previousCapture.clockEpoch ||
|
|
615
|
+
!sameCaptureClock && browserCapture.epochStartedAt > (previousCapture.epochStartedAt ?? 0) ||
|
|
616
|
+
sameCaptureClock && (sameCaptureEpoch && browserCapture.revision > previousCapture.revision ||
|
|
617
|
+
!sameCaptureEpoch && browserCapture.epochStartedAt > (previousCapture.epochStartedAt ?? 0)));
|
|
618
|
+
const staleCapture = captureOrderKnown && previousCapture && !newerCapture;
|
|
567
619
|
const incomingAppendService = typeof appendEpoch === "string" && appendEpoch.length > 0
|
|
568
620
|
? appendServiceSnapshot(appendServiceReading) : null;
|
|
569
621
|
const incomingFragmentService = typeof appendEpoch === "string" && appendEpoch.length > 0
|
|
@@ -578,37 +630,85 @@ export class Viewer {
|
|
|
578
630
|
(!previous?.appendService || appendServiceIsMonotonic(incomingAppendService, previous.appendService)) &&
|
|
579
631
|
(!previous?.fragmentService || fragmentServiceIsMonotonic(incomingFragmentService, previous.fragmentService))
|
|
580
632
|
: appendEpochStartedAt > (previous?.appendEpochStartedAt ?? 0));
|
|
633
|
+
const preserveBrowserService = sameAppendEpoch && incomingAppendService && incomingFragmentService;
|
|
581
634
|
const hasPlaybackSample = Number.isFinite(sampleMbps) && sampleMbps > 0 &&
|
|
582
|
-
Number.isFinite(sampleAt) && sampleAt > 0
|
|
583
|
-
|
|
635
|
+
Number.isFinite(sampleAt) && sampleAt > 0 && Number.isFinite(captureClockEpoch) &&
|
|
636
|
+
Number.isFinite(linkSampleClockEpoch) && linkSampleClockEpoch === captureClockEpoch &&
|
|
637
|
+
Number.isSafeInteger(linkSampleRevision) && linkSampleRevision > 0 &&
|
|
638
|
+
Number.isFinite(linkSampleStartedAtBrowser) && Number.isFinite(linkSampleCompletedAtBrowser) &&
|
|
639
|
+
linkSampleStartedAtBrowser >= captureClockEpoch && linkSampleCompletedAtBrowser === sampleAt &&
|
|
640
|
+
linkSampleStartedAtBrowser <= linkSampleCompletedAtBrowser &&
|
|
641
|
+
Number.isFinite(captureCompletedAtBrowser) && sampleAt <= captureCompletedAtBrowser &&
|
|
642
|
+
Number.isFinite(linkSampleAgeMs) && linkSampleAgeMs >= 0;
|
|
643
|
+
const oldLinkEpoch = this.netReport?.linkCapture?.epoch !== String(linkSampleClockEpoch) &&
|
|
644
|
+
Number.isFinite(this.netReport?.linkCapture?.epochStartedAt) &&
|
|
645
|
+
Number.isFinite(linkSampleClockEpoch) &&
|
|
646
|
+
linkSampleClockEpoch <= this.netReport.linkCapture.epochStartedAt;
|
|
647
|
+
const newerPlaybackSample = hasPlaybackSample && !oldLinkEpoch &&
|
|
648
|
+
(linkSampleClockEpoch !== this.netReport?.linkCapture?.clockEpoch ||
|
|
649
|
+
linkSampleRevision > (this.netReport?.linkCapture?.revision ?? 0));
|
|
650
|
+
const linkClockEpochChanged = Number.isFinite(captureClockEpoch) &&
|
|
651
|
+
Number.isFinite(this.netReport?.linkCapture?.clockEpoch) &&
|
|
652
|
+
captureClockEpoch !== this.netReport.linkCapture.clockEpoch;
|
|
653
|
+
const newerInvalidPlaybackSample = !stale && newerCapture && !hasPlaybackSample &&
|
|
654
|
+
(Number.isSafeInteger(linkSampleRevision) || linkSampleAt !== null && linkSampleAt !== undefined ||
|
|
655
|
+
linkClockEpochChanged);
|
|
656
|
+
const preserveCapture = stale || staleCapture;
|
|
657
|
+
const linkCapture = newerPlaybackSample ? {
|
|
658
|
+
owner: "browser", clock: "performance", clockEpoch: linkSampleClockEpoch,
|
|
659
|
+
startedAt: linkSampleStartedAtBrowser, completedAt: linkSampleCompletedAtBrowser,
|
|
660
|
+
actionId: typeof linkSampleActionId === "string" ? linkSampleActionId : null,
|
|
661
|
+
generation: Number.isSafeInteger(linkSampleGeneration) ? linkSampleGeneration : null,
|
|
662
|
+
epoch: String(linkSampleClockEpoch), epochStartedAt: linkSampleClockEpoch,
|
|
663
|
+
revision: linkSampleRevision, validity: "valid", unknownReason: null
|
|
664
|
+
} : newerInvalidPlaybackSample ? {
|
|
665
|
+
owner: "browser", clock: "performance", clockEpoch: Number.isFinite(captureClockEpoch) ? captureClockEpoch : null,
|
|
666
|
+
startedAt: null, completedAt: null, actionId: appendSessionId,
|
|
667
|
+
generation: Number.isSafeInteger(generation) ? generation : null,
|
|
668
|
+
epoch: Number.isFinite(captureClockEpoch) ? String(captureClockEpoch) : null,
|
|
669
|
+
epochStartedAt: captureClockEpoch,
|
|
670
|
+
revision: linkSampleRevision, validity: "invalid", unknownReason: "invalid-link-sample-capture"
|
|
671
|
+
} : this.netReport?.linkCapture ?? {
|
|
672
|
+
owner: "browser", clock: "performance", clockEpoch: null, startedAt: null, completedAt: null,
|
|
673
|
+
actionId: appendSessionId, generation: Number.isSafeInteger(generation) ? generation : null,
|
|
674
|
+
epoch: null, epochStartedAt: null, revision: null, validity: "unknown",
|
|
675
|
+
unknownReason: "missing-link-sample-capture"
|
|
676
|
+
};
|
|
677
|
+
const useCapture = !stale && !staleCapture && newerCapture && browserCapture.validity === "valid";
|
|
584
678
|
this.netReport = {
|
|
585
679
|
linkMbps: lastLinkMbps,
|
|
586
680
|
linkMeasuredAt,
|
|
587
|
-
linkSampleAt: newerPlaybackSample ? sampleAt : this.netReport?.linkSampleAt ?? null,
|
|
588
|
-
linkSampleMbps: newerPlaybackSample ? sampleMbps : this.netReport?.linkSampleMbps ?? null,
|
|
589
|
-
linkSampleMeasuredAt: newerPlaybackSample
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
:
|
|
681
|
+
linkSampleAt: newerPlaybackSample ? sampleAt : newerInvalidPlaybackSample ? null : this.netReport?.linkSampleAt ?? null,
|
|
682
|
+
linkSampleMbps: newerPlaybackSample ? sampleMbps : newerInvalidPlaybackSample ? null : this.netReport?.linkSampleMbps ?? null,
|
|
683
|
+
linkSampleMeasuredAt: newerPlaybackSample ? now : newerInvalidPlaybackSample ? null : this.netReport?.linkSampleMeasuredAt ?? null,
|
|
684
|
+
linkCapture,
|
|
685
|
+
bufferCapture: preserveCapture ? previousCapture ?? browserCapture : useCapture ? browserCapture : {
|
|
686
|
+
...browserCapture,
|
|
687
|
+
validity: "unknown",
|
|
688
|
+
unknownReason: browserCapture.unknownReason ?? "missing-or-invalid-browser-capture"
|
|
689
|
+
},
|
|
594
690
|
appendSessionId: useBrowserService ? appendSessionId : previous?.appendSessionId ?? null,
|
|
595
|
-
appendEpoch: useBrowserService ? appendEpoch : previous?.appendEpoch ?? null
|
|
691
|
+
appendEpoch: useBrowserService ? appendEpoch : preserveBrowserService ? previous?.appendEpoch ?? null
|
|
692
|
+
: useCapture ? null : previous?.appendEpoch ?? null,
|
|
596
693
|
appendEpochStartedAt: useBrowserService ? appendEpochStartedAt : previous?.appendEpochStartedAt ?? null,
|
|
597
694
|
appendSnapshotAt: useBrowserService ? appendSnapshotAt : previous?.appendSnapshotAt ?? null,
|
|
598
|
-
appendService: useBrowserService ? incomingAppendService : previous?.appendService ?? null
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
695
|
+
appendService: useBrowserService ? incomingAppendService : preserveBrowserService ? previous?.appendService ?? null
|
|
696
|
+
: useCapture ? null : previous?.appendService ?? null,
|
|
697
|
+
fragmentService: useBrowserService ? incomingFragmentService : preserveBrowserService ? previous?.fragmentService ?? null
|
|
698
|
+
: useCapture ? null : previous?.fragmentService ?? null,
|
|
699
|
+
appendServiceReceivedAt: useBrowserService ? now : preserveBrowserService ? previous?.appendServiceReceivedAt ?? null
|
|
700
|
+
: useCapture ? null : previous?.appendServiceReceivedAt ?? null,
|
|
701
|
+
bufferedAheadSec: preserveCapture ? previous?.bufferedAheadSec ?? 0 : useCapture ? held : 0,
|
|
602
702
|
timestampOffsets: Object.fromEntries(["video", "audio"].filter((key) =>
|
|
603
703
|
Number.isFinite(timestampOffsets?.[key])).map((key) => [key, timestampOffsets[key]])),
|
|
604
|
-
bufferedRanges: bufferedRanges && typeof bufferedRanges === "object" ?
|
|
704
|
+
bufferedRanges: preserveCapture ? previous?.bufferedRanges ?? null : useCapture && bufferedRanges && typeof bufferedRanges === "object" ?
|
|
605
705
|
Object.fromEntries(["video", "audio", "media"].filter((key) => Array.isArray(bufferedRanges[key]))
|
|
606
706
|
.map((key) => [key, bufferedRanges[key].slice(0, 256).filter((range) =>
|
|
607
707
|
Number.isFinite(range?.start) && Number.isFinite(range?.end) && range.start >= 0 && range.end > range.start)
|
|
608
708
|
.map(({ start, end }) => ({ start, end }))])) : null,
|
|
609
|
-
bufferLimitSeconds: Number.isFinite(bufferLimitSeconds) && bufferLimitSeconds > 0
|
|
709
|
+
bufferLimitSeconds: preserveCapture ? previous?.bufferLimitSeconds ?? null : useCapture && Number.isFinite(bufferLimitSeconds) && bufferLimitSeconds > 0
|
|
610
710
|
? bufferLimitSeconds
|
|
611
|
-
: this.netReport?.bufferLimitSeconds ?? null,
|
|
711
|
+
: useCapture ? null : this.netReport?.bufferLimitSeconds ?? null,
|
|
612
712
|
positionSeconds:
|
|
613
713
|
Number.isFinite(positionSeconds) && positionSeconds >= 0 ? positionSeconds : null,
|
|
614
714
|
at: now
|
|
@@ -46,11 +46,15 @@ export class ViewerPlaybackState {
|
|
|
46
46
|
return removed;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
addLinkReading(state, at, mbps) {
|
|
50
|
-
state.link.add(at, mbps * 1_000_000);
|
|
49
|
+
addLinkReading(state, at, mbps, capture = null) {
|
|
50
|
+
state.link.add(at, mbps * 1_000_000, capture);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
forgetLinkReadings(state, playbackReadiness, lookaheadSeconds) {
|
|
54
|
+
if (state) state.link = new playbackReadiness.RateTrend(lookaheadSeconds);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sourceRateReadings(state, id, stats, now, playbackReadiness, lookaheadSeconds, capture = null) {
|
|
54
58
|
let source = state.sources.get(id);
|
|
55
59
|
if (!source) {
|
|
56
60
|
const { RateTrend, SessionRate } = playbackReadiness;
|
|
@@ -58,15 +62,16 @@ export class ViewerPlaybackState {
|
|
|
58
62
|
deliveredAll: new SessionRate(), freeAll: new SessionRate(), limit: null };
|
|
59
63
|
state.sources.set(id, source);
|
|
60
64
|
}
|
|
65
|
+
const readingCapture = capture ?? stats?.sourceCapture ?? null;
|
|
61
66
|
if (Number.isFinite(stats?.downloadSpeed) && stats.downloadSpeed >= 0) {
|
|
62
|
-
source.delivered.add(now, stats.downloadSpeed);
|
|
63
|
-
source.deliveredAll.add(now, stats.downloadSpeed);
|
|
67
|
+
source.delivered.add(now, stats.downloadSpeed, readingCapture);
|
|
68
|
+
source.deliveredAll.add(now, stats.downloadSpeed, readingCapture);
|
|
64
69
|
}
|
|
65
70
|
const limit = stats?.selectionLimit;
|
|
66
71
|
if (limit && source.limit && limit.freeMs > source.limit.freeMs) {
|
|
67
72
|
const free = Math.max(0, limit.freeBytes - source.limit.freeBytes) * 1000 / (limit.freeMs - source.limit.freeMs);
|
|
68
|
-
source.free.add(now, free);
|
|
69
|
-
source.freeAll.add(now, free);
|
|
73
|
+
source.free.add(now, free, readingCapture);
|
|
74
|
+
source.freeAll.add(now, free, readingCapture);
|
|
70
75
|
}
|
|
71
76
|
if (limit) source.limit = limit;
|
|
72
77
|
return sourceService(source.free.snapshot(), source.delivered.snapshot(),
|
|
@@ -85,6 +90,19 @@ export class ViewerPlaybackState {
|
|
|
85
90
|
return source ? { free: source.free.snapshot(), delivered: source.delivered.snapshot() } : null;
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
sourceCaptureSnapshot(state, id) {
|
|
94
|
+
const source = state.sources.get(id);
|
|
95
|
+
if (!source) return null;
|
|
96
|
+
return {
|
|
97
|
+
recent: { delivered: source.delivered.captureSnapshot(), free: source.free.captureSnapshot() },
|
|
98
|
+
tail: { delivered: source.deliveredAll.captureSnapshot(), free: source.freeAll.captureSnapshot() }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
forgetSourceReadings(state, id) {
|
|
103
|
+
state?.sources.delete(id);
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
getWait(viewer, waitId) {
|
|
89
107
|
return this.#stateFor(viewer)?.waits.get(waitId) ?? null;
|
|
90
108
|
}
|