@torrent-tv/proxy 2.89.17 → 2.89.18
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/README.md +14 -3
- package/package.json +10 -2
- package/server.js +0 -11
- package/.claude/commands/opsx/apply.md +0 -155
- package/.claude/commands/opsx/archive.md +0 -160
- package/.claude/commands/opsx/explore.md +0 -174
- package/.claude/commands/opsx/propose.md +0 -109
- package/.claude/commands/opsx/sync.md +0 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +0 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +0 -117
- package/.claude/skills/openspec-explore/SKILL.md +0 -289
- package/.claude/skills/openspec-propose/SKILL.md +0 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +0 -147
- package/.dockerignore +0 -5
- package/AGENTS.md +0 -363
- package/CHANGELOG.md +0 -2377
- package/Dockerfile +0 -30
- package/biome.json +0 -412
- package/corpus.tmp.json +0 -1
- package/docs/browser-proxy-contract.md +0 -155
- package/docs/container-architecture.md +0 -273
- package/docs/download-architecture.md +0 -261
- package/docs/encode-architecture.md +0 -765
- package/docs/encode-run-state.md +0 -96
- package/docs/logs.md +0 -151
- package/docs/playback-start-forecast.md +0 -113
- package/docs/socks5-egress-research.md +0 -114
- package/docs/storage-architecture.md +0 -273
- package/knip.json +0 -14
- package/main2.cpuprofile +0 -1
- package/openspec/changes/adaptive-bitrate/.openspec.yaml +0 -0
- package/openspec/changes/adaptive-bitrate/design.md +0 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +0 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +0 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +0 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +0 -2
- package/openspec/changes/binary-distribution/proposal.md +0 -53
- package/openspec/changes/chunked-request-bodies/design.md +0 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +0 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +0 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +0 -40
- package/openspec/changes/cold-start/design.md +0 -98
- package/openspec/changes/cold-start/proposal.md +0 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +0 -47
- package/openspec/changes/cold-start/tasks.md +0 -48
- package/openspec/changes/disk-cap/.openspec.yaml +0 -2
- package/openspec/changes/disk-cap/proposal.md +0 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +0 -25
- package/openspec/changes/disk-cap/tasks.md +0 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +0 -2
- package/openspec/changes/proxy-observability/design.md +0 -38
- package/openspec/changes/proxy-observability/proposal.md +0 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +0 -33
- package/openspec/changes/proxy-observability/tasks.md +0 -21
- package/openspec/changes/seek-debounce/.openspec.yaml +0 -0
- package/openspec/changes/seek-debounce/design.md +0 -118
- package/openspec/changes/seek-debounce/proposal.md +0 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +0 -41
- package/openspec/changes/seek-debounce/tasks.md +0 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +0 -2
- package/openspec/changes/subtitle-language/proposal.md +0 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +0 -34
- package/openspec/changes/subtitle-language/tasks.md +0 -26
- package/openspec/changes/track-selection/.openspec.yaml +0 -2
- package/openspec/changes/track-selection/design.md +0 -47
- package/openspec/changes/track-selection/proposal.md +0 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +0 -41
- package/openspec/changes/track-selection/tasks.md +0 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +0 -2
- package/openspec/changes/transcode-quality/proposal.md +0 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +0 -139
- package/openspec/changes/transcode-quality/tasks.md +0 -63
- package/openspec/config.yaml +0 -38
- package/public/.well-known/appspecific/com.chrome.devtools.json +0 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +0 -329
- package/research/double-spawn-2026-09-10.md +0 -171
- package/research/handover-2026-09-13.md +0 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +0 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +0 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +0 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +0 -225
- package/research/valgrind-replica-2026-08-27/logs.tar.gz +0 -0
- package/scripts/build-audio-samples.mjs +0 -31
- package/scripts/render-run-graph.js +0 -173
- package/test/asset-paths.test.js +0 -28
- package/test/audio-buffer-continuity.test.js +0 -77
- package/test/audio-calibration.test.js +0 -23
- package/test/audio-inventory.test.js +0 -212
- package/test/audio-track-bitrate.test.js +0 -40
- package/test/audio-warm-recorded.test.js +0 -63
- package/test/auto-quality-step.test.js +0 -762
- package/test/available-share.test.js +0 -76
- package/test/background-fill.test.js +0 -190
- package/test/client-log-file.test.js +0 -126
- package/test/client-logs-route.test.js +0 -34
- package/test/coalesce.test.js +0 -39
- package/test/completed-files.test.js +0 -115
- package/test/concurrent-cost.test.js +0 -148
- package/test/container-media-info.test.js +0 -228
- package/test/contention.test.js +0 -76
- package/test/core-dumps.test.js +0 -57
- package/test/coverage-follows-the-disk.test.js +0 -233
- package/test/coverage-map.test.js +0 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +0 -58
- package/test/cut-times-timeline.test.js +0 -65
- package/test/cuts-follow-published-grid.test.js +0 -109
- package/test/data-channel-frame.test.js +0 -63
- package/test/data-channel-viewer-lifecycle.test.js +0 -193
- package/test/dead-torrent-handle.test.js +0 -52
- package/test/declared-rates.test.js +0 -116
- package/test/declared-tracks.test.js +0 -43
- package/test/decode-cost-fit.test.js +0 -202
- package/test/decode-cost.test.js +0 -475
- package/test/delivery-probe-field.test.js +0 -173
- package/test/delivery-probe.test.js +0 -382
- package/test/demand-register.test.js +0 -206
- package/test/dht-bootstrap.test.js +0 -72
- package/test/drain-threat.test.js +0 -65
- package/test/duplicate-add.test.js +0 -48
- package/test/encode-admission.test.js +0 -170
- package/test/encode-cost.test.js +0 -275
- package/test/encode-exit.test.js +0 -111
- package/test/encode-orchestrator.test.js +0 -426
- package/test/encode-plan-viewers.test.js +0 -725
- package/test/encode-plan.test.js +0 -545
- package/test/encode-run-state.test.js +0 -314
- package/test/encode-run.test.js +0 -248
- package/test/encode-slope.test.js +0 -46
- package/test/encoded-output.test.js +0 -39
- package/test/encoder-kinds.test.js +0 -122
- package/test/encoder-readings.test.js +0 -68
- package/test/episode-naming.test.js +0 -131
- package/test/every-claimant-is-told-its-share.test.js +0 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +0 -354
- package/test/failed-start-breaker.test.js +0 -202
- package/test/fastest-wires.test.js +0 -143
- package/test/ffmpeg-stream-counts.test.js +0 -101
- package/test/file-edges.test.js +0 -191
- package/test/files-in-use.test.js +0 -34
- package/test/fixtures/audio-frame-boundary.json +0 -1
- package/test/fixtures/refused-audio-20261002.json +0 -10
- package/test/fmp4-presentation-clock.test.js +0 -148
- package/test/fmp4-self-contained.test.js +0 -80
- package/test/fmp4-timestamps.test.js +0 -140
- package/test/fmp4-track-completeness.test.js +0 -41
- package/test/format-keyed-outputs.test.js +0 -78
- package/test/health-metrics.test.js +0 -36
- package/test/held-read-ownership.test.js +0 -77
- package/test/held-request-width.test.js +0 -157
- package/test/helpers/encode-run.js +0 -248
- package/test/helpers/held-file.js +0 -77
- package/test/helpers/manager.js +0 -62
- package/test/helpers/matroska-file.js +0 -296
- package/test/helpers/output-spec.js +0 -63
- package/test/host-load.test.js +0 -68
- package/test/index-accuracy-is-about-the-file.test.js +0 -128
- package/test/init-compat.test.js +0 -219
- package/test/input-lost-quiets-the-plan.test.js +0 -261
- package/test/input-unavailable.test.js +0 -48
- package/test/installation-id.test.js +0 -39
- package/test/keeping-period.test.js +0 -83
- package/test/keyframe-index-accuracy.test.js +0 -188
- package/test/keyframe-table-budget.test.js +0 -123
- package/test/keyframes-belong-to-the-file.test.js +0 -249
- package/test/learned-median.test.js +0 -37
- package/test/letting-go-asks-who-is-watching.test.js +0 -191
- package/test/limit-row-by-area.test.js +0 -60
- package/test/link-budget.test.js +0 -111
- package/test/link-probe.test.js +0 -56
- package/test/local-encoding-data.test.js +0 -273
- package/test/logger-repeats.test.js +0 -134
- package/test/look-ahead-coverage.test.js +0 -56
- package/test/manager-surface.test.js +0 -291
- package/test/matroska-blocks.test.js +0 -0
- package/test/matroska-cluster-discovery.test.js +0 -323
- package/test/matroska-cues-track.test.js +0 -192
- package/test/media-time.test.js +0 -62
- package/test/memory-budget.test.js +0 -390
- package/test/move-cost.test.js +0 -216
- package/test/mp4-composition-times.test.js +0 -0
- package/test/mp4-subtitles.test.js +0 -173
- package/test/one-authority.test.js +0 -285
- package/test/one-budget.test.js +0 -149
- package/test/one-output-one-session.test.js +0 -273
- package/test/one-resource-divided.test.js +0 -185
- package/test/one-serving-output-per-height.test.js +0 -458
- package/test/orchestrator-wired.test.js +0 -207
- package/test/output-catalog.test.js +0 -39
- package/test/output-shape.test.js +0 -21
- package/test/output-spec.test.js +0 -251
- package/test/output-speed.test.js +0 -92
- package/test/outputs-being-prepared.test.js +0 -40
- package/test/packet-witness-ring.test.js +0 -236
- package/test/packet-witness.test.js +0 -148
- package/test/piece-completeness.test.js +0 -78
- package/test/piece-disk-store.test.js +0 -401
- package/test/piece-from-whole-file.test.js +0 -129
- package/test/piece-lru.test.js +0 -319
- package/test/piece-reader.test.js +0 -162
- package/test/piece-selection-offset.test.js +0 -85
- package/test/piece-store-eviction.test.js +0 -595
- package/test/piece-store-never-refuses.test.js +0 -153
- package/test/piece-store-reservations.test.js +0 -403
- package/test/piece-store-slow-disk.test.js +0 -159
- package/test/piece-tail.test.js +0 -97
- package/test/plan-host-timings.test.js +0 -68
- package/test/plan-is-steady.test.js +0 -225
- package/test/playback-readiness.test.js +0 -518
- package/test/playback-start-regressions.test.js +0 -101
- package/test/priority-map-download.test.js +0 -174
- package/test/priority-map-emptied.test.js +0 -204
- package/test/priority-map-per-output.test.js +0 -215
- package/test/priority-map.test.js +0 -163
- package/test/probe-wedge-certainty.test.js +0 -60
- package/test/produced-copy-choice.test.js +0 -213
- package/test/published-timeline.test.js +0 -83
- package/test/quality-offer.test.js +0 -160
- package/test/quality-variants.test.js +0 -1274
- package/test/rate-control-in-the-output.test.js +0 -181
- package/test/read-survives-withdrawal.test.js +0 -164
- package/test/read-window.test.js +0 -365
- package/test/reads-not-yet-arrived.test.js +0 -218
- package/test/resume-warm.test.js +0 -39
- package/test/run-graph-drift.test.js +0 -37
- package/test/run-inputs.test.js +0 -80
- package/test/run-intervals.test.js +0 -100
- package/test/run-position-follows-published-grid.test.js +0 -89
- package/test/run-progress.test.js +0 -64
- package/test/same-height-switch.test.js +0 -663
- package/test/seek-frees-the-viewers-requests.test.js +0 -217
- package/test/seek-landing.test.js +0 -109
- package/test/seek-target-not-superseded.test.js +0 -195
- package/test/segment-closed-on-both-branches.test.js +0 -114
- package/test/segment-cut-times.test.js +0 -63
- package/test/segment-demand.test.js +0 -80
- package/test/segment-hold-supersede.test.js +0 -91
- package/test/segment-judged-by-its-own-header.test.js +0 -222
- package/test/segment-serve-wiring.test.js +0 -481
- package/test/segment-store-eviction.test.js +0 -247
- package/test/segment-store.test.js +0 -352
- package/test/segment-true-start.test.js +0 -86
- package/test/segments-are-shared.test.js +0 -176
- package/test/send-chunk-bytes.test.js +0 -86
- package/test/serving-existing-output.test.js +0 -111
- package/test/serving-output.test.js +0 -102
- package/test/shared-piece-store.test.js +0 -310
- package/test/sidecar-naming.test.js +0 -142
- package/test/soundtrack-rate-bound.test.js +0 -289
- package/test/source-file.test.js +0 -133
- package/test/source-files-route.test.js +0 -113
- package/test/source-registry.test.js +0 -18
- package/test/split-a-stretch.test.js +0 -139
- package/test/stale-request-after-seek.test.js +0 -158
- package/test/startup-readings.test.js +0 -124
- package/test/stream-route.test.js +0 -237
- package/test/subtitle-cue-framing.test.js +0 -200
- package/test/subtitle-cue-source.test.js +0 -102
- package/test/subtitle-cue-walk.test.js +0 -385
- package/test/subtitle-cursor.test.js +0 -63
- package/test/subtitle-defaults.test.js +0 -118
- package/test/subtitle-language.test.js +0 -252
- package/test/subtitle-metadata.test.js +0 -15
- package/test/subtitle-plan-waits.test.js +0 -125
- package/test/subtitle-track-numbering.test.js +0 -391
- package/test/supply-margin.test.js +0 -190
- package/test/swarm-follows-readers.test.js +0 -174
- package/test/swarm-reach.test.js +0 -130
- package/test/swarm-selection.test.js +0 -220
- package/test/tail-duplication.test.js +0 -167
- package/test/the-channel-has-both-ends.test.js +0 -91
- package/test/the-torrent-thread-serves-bytes.test.js +0 -107
- package/test/thread-pool.test.js +0 -53
- package/test/timeline.test.js +0 -84
- package/test/torrent-contents.test.js +0 -235
- package/test/torrent-cost.test.js +0 -71
- package/test/torrent-files.test.js +0 -221
- package/test/torrent-naming.test.js +0 -255
- package/test/tracks-begin-together.test.js +0 -209
- package/test/tunnel-renewal.test.js +0 -167
- package/test/two-viewers-one-picture.test.js +0 -508
- package/test/upload-hurry.test.js +0 -186
- package/test/usrsctp-state.test.js +0 -131
- package/test/video-facts.test.js +0 -102
- package/test/viewer-assignments.test.js +0 -143
- package/test/viewer-outputs.test.js +0 -285
- package/test/viewer-presence.test.js +0 -116
- package/test/viewer-requests-admission.test.js +0 -92
- package/test/viewer-starts-on-map.test.js +0 -27
- package/test/viewer-states.test.js +0 -192
- package/test/viewer-subtitle-subscription.test.js +0 -96
- package/test/viewer.test.js +0 -139
- package/test/viewing-generations.test.js +0 -390
- package/test/visible-rung.test.js +0 -37
- package/test/wait-ledger.test.js +0 -100
- package/test/webrtc-shim.test.js +0 -149
- package/test/wedge-certainty.test.js +0 -131
- package/test/whole-file-held-read.test.js +0 -30
- package/test/withdraw-piece-claim.test.js +0 -212
- package/test/worker-channel.test.js +0 -158
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What the download is told when the priority map changes.
|
|
3
|
-
*
|
|
4
|
-
* The map states seconds of film against a number. This is the one place those
|
|
5
|
-
* seconds become bytes, and the one place the map's own scale — as many bands
|
|
6
|
-
* as the film needs — is fitted onto the five levels the register has. Both
|
|
7
|
-
* were written on 2026-09-05 to replace the windows the reads themselves used
|
|
8
|
-
* to declare: fifteen reads on one file were fifteen windows on a piece store
|
|
9
|
-
* holding sixteen pieces, and half of all evictions then took a piece a reader
|
|
10
|
-
* had said it wanted.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import test from "node:test";
|
|
14
|
-
import assert from "node:assert/strict";
|
|
15
|
-
import { TorrentPool } from "../services/torrent/torrent-pool.js";
|
|
16
|
-
import { demandFor, forgetTorrent } from "../services/torrent/download/registry.js";
|
|
17
|
-
import { Urgency } from "../services/torrent/demand/index.js";
|
|
18
|
-
import { mapForViewer, runsOf } from "../services/viewer/PriorityMap.js";
|
|
19
|
-
|
|
20
|
-
/** A torrent that is nothing but one file of a known length. */
|
|
21
|
-
function torrentOf({ length = 1_000_000, offset = 0 } = {}) {
|
|
22
|
-
return {
|
|
23
|
-
infoHash: `hash-${Math.random().toString(36).slice(2)}`,
|
|
24
|
-
pieceLength: 1024,
|
|
25
|
-
files: [{ offset, length, name: "film.mkv" }],
|
|
26
|
-
_selections: { _items: [] },
|
|
27
|
-
_select() {},
|
|
28
|
-
_deselect() {},
|
|
29
|
-
critical() {}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** The pool's method under test, called without building a pool. */
|
|
34
|
-
const applyPriorityMap = TorrentPool.prototype.applyPriorityMap;
|
|
35
|
-
|
|
36
|
-
test("seconds of film become bytes of the file", () => {
|
|
37
|
-
const torrent = torrentOf({ length: 1000 });
|
|
38
|
-
try {
|
|
39
|
-
applyPriorityMap.call(null, torrent, 0, [
|
|
40
|
-
{ from: 0, to: 100, priority: 5 },
|
|
41
|
-
{ from: 100, to: 200, priority: 3 }
|
|
42
|
-
], 200);
|
|
43
|
-
|
|
44
|
-
const { register } = demandFor(torrent);
|
|
45
|
-
const stated = register.windows().filter((one) => String(one.claimant).startsWith("priority-map:"));
|
|
46
|
-
assert.equal(stated.length, 2);
|
|
47
|
-
const first = stated.find((one) => one.byteStart === 0);
|
|
48
|
-
assert.equal(first.byteEnd, 499, "half the film is half the file");
|
|
49
|
-
const second = stated.find((one) => one.byteStart === 500);
|
|
50
|
-
assert.equal(second.byteEnd, 999, "the last byte of the file, not one past it");
|
|
51
|
-
} finally {
|
|
52
|
-
forgetTorrent(torrent);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("a zone is stated at the level it means, not at the level of its position", () => {
|
|
57
|
-
const torrent = torrentOf();
|
|
58
|
-
try {
|
|
59
|
-
// A viewer 300 s into a 3000 s film: one band behind them that nobody is
|
|
60
|
-
// approaching, then bands of decreasing urgency ahead.
|
|
61
|
-
const zones = runsOf(mapForViewer({
|
|
62
|
-
atSeconds: 300,
|
|
63
|
-
durationSeconds: 3000,
|
|
64
|
-
allowanceSeconds: 10
|
|
65
|
-
}));
|
|
66
|
-
assert.ok(zones.length > 5, "the map should be finer than the register's levels");
|
|
67
|
-
|
|
68
|
-
applyPriorityMap.call(null, torrent, 0, zones, 3000);
|
|
69
|
-
const { register } = demandFor(torrent);
|
|
70
|
-
const stated = register.windows().filter((one) => String(one.claimant).startsWith("priority-map:"));
|
|
71
|
-
|
|
72
|
-
const near = stated.filter((one) => one.urgency === Urgency.NEAR);
|
|
73
|
-
assert.equal(near.length, 1, "exactly one band is where the viewer is");
|
|
74
|
-
assert.equal(near[0].byteStart, Math.floor((300 / 3000) * 1_000_000));
|
|
75
|
-
|
|
76
|
-
const behind = stated.filter((one) => one.urgency === Urgency.BEHIND);
|
|
77
|
-
assert.equal(behind.length, 1, "what nobody is approaching is the one thing wanted last");
|
|
78
|
-
assert.equal(behind[0].byteStart, 0, "and it is the stretch behind the viewer");
|
|
79
|
-
|
|
80
|
-
assert.ok(
|
|
81
|
-
stated.some((one) => one.urgency === Urgency.AHEAD),
|
|
82
|
-
"the lead being built was stated at no level of its own"
|
|
83
|
-
);
|
|
84
|
-
assert.ok(
|
|
85
|
-
stated.every((one) => one.urgency !== Urgency.BLOCKED),
|
|
86
|
-
"the map claimed BLOCKED, which only a reader stopped on those bytes may say"
|
|
87
|
-
);
|
|
88
|
-
} finally {
|
|
89
|
-
forgetTorrent(torrent);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("a viewer who has stopped the picture makes the whole film wanted last", () => {
|
|
94
|
-
const torrent = torrentOf();
|
|
95
|
-
try {
|
|
96
|
-
const zones = runsOf(mapForViewer({
|
|
97
|
-
atSeconds: 300,
|
|
98
|
-
durationSeconds: 3000,
|
|
99
|
-
allowanceSeconds: 10,
|
|
100
|
-
playing: false
|
|
101
|
-
}));
|
|
102
|
-
applyPriorityMap.call(null, torrent, 0, zones, 3000);
|
|
103
|
-
|
|
104
|
-
const { register } = demandFor(torrent);
|
|
105
|
-
const stated = register.windows().filter((one) => String(one.claimant).startsWith("priority-map:"));
|
|
106
|
-
// A PAUSE REMOVES THE TIME, NOT THE DIRECTION. What they have watched is
|
|
107
|
-
// behind them and what they have not is in front, exactly as while they were
|
|
108
|
-
// watching; what changes is that nothing has a time any more, so every band
|
|
109
|
-
// of theirs yields to anybody who is on their way somewhere.
|
|
110
|
-
assert.ok(stated.length >= 2, "their position survives the pause");
|
|
111
|
-
for (const one of stated) {
|
|
112
|
-
assert.ok(
|
|
113
|
-
one.urgency === Urgency.BEHIND || one.urgency === Urgency.TAIL,
|
|
114
|
-
"and nothing of theirs is urgent, because nobody is coming"
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
const front = stated.filter((one) => one.urgency === Urgency.TAIL);
|
|
118
|
-
assert.ok(front.length >= 1, "the film in front of them is still in front");
|
|
119
|
-
assert.ok(
|
|
120
|
-
front[0].byteStart >= Math.floor((300 / 3000) * 1_000_000),
|
|
121
|
-
"and it begins where they stopped"
|
|
122
|
-
);
|
|
123
|
-
// Wanted last ABSOLUTELY, not relative to this film's own map. The two
|
|
124
|
-
// speculative levels are withheld across every torrent at once while
|
|
125
|
-
// anything urgent is missing anywhere, so this has to lose to a film
|
|
126
|
-
// somebody is actually watching — and judged against itself alone it would
|
|
127
|
-
// be the most urgent thing there is.
|
|
128
|
-
const behind = stated.filter((one) => one.urgency === Urgency.BEHIND);
|
|
129
|
-
assert.equal(behind.length, 1, "what they have already watched is wanted last of all");
|
|
130
|
-
assert.equal(behind[0].byteStart, 0, "and it is the stretch from the beginning to where they stopped");
|
|
131
|
-
} finally {
|
|
132
|
-
forgetTorrent(torrent);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("a map with fewer bands than the last one leaves nothing stated behind", () => {
|
|
137
|
-
const torrent = torrentOf();
|
|
138
|
-
try {
|
|
139
|
-
applyPriorityMap.call(null, torrent, 0, [
|
|
140
|
-
{ from: 0, to: 100, priority: 5 },
|
|
141
|
-
{ from: 100, to: 200, priority: 4 },
|
|
142
|
-
{ from: 200, to: 300, priority: 3 }
|
|
143
|
-
], 300);
|
|
144
|
-
const { register } = demandFor(torrent);
|
|
145
|
-
assert.equal(
|
|
146
|
-
register.windows().filter((one) => String(one.claimant).startsWith("priority-map:")).length,
|
|
147
|
-
3
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
applyPriorityMap.call(null, torrent, 0, [{ from: 0, to: 300, priority: 5 }], 300);
|
|
151
|
-
assert.equal(
|
|
152
|
-
register.windows().filter((one) => String(one.claimant).startsWith("priority-map:")).length,
|
|
153
|
-
1,
|
|
154
|
-
"bands the map no longer has went on being asked for"
|
|
155
|
-
);
|
|
156
|
-
} finally {
|
|
157
|
-
forgetTorrent(torrent);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("a file of unknown length or duration is not guessed at", () => {
|
|
162
|
-
const torrent = torrentOf();
|
|
163
|
-
try {
|
|
164
|
-
applyPriorityMap.call(null, torrent, 0, [{ from: 0, to: 100, priority: 5 }], 0);
|
|
165
|
-
applyPriorityMap.call(null, torrent, 7, [{ from: 0, to: 100, priority: 5 }], 300);
|
|
166
|
-
const { register } = demandFor(torrent);
|
|
167
|
-
assert.equal(
|
|
168
|
-
register.windows().filter((one) => String(one.claimant).startsWith("priority-map:")).length,
|
|
169
|
-
0
|
|
170
|
-
);
|
|
171
|
-
} finally {
|
|
172
|
-
forgetTorrent(torrent);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file WHEN NOBODY WANTS A FILE ANY MORE, THE MAP SAYS SO.
|
|
3
|
-
*
|
|
4
|
-
* The map is the only statement of what is wanted, and until 2.83.4 it could
|
|
5
|
-
* make only one of the two statements it exists for. A file whose viewers had
|
|
6
|
-
* gone was deleted from the orchestrator's own memory and published nowhere, so
|
|
7
|
-
* the bands it had stated on the swarm's behalf stood until the torrent itself
|
|
8
|
-
* was removed — read from the demand register, a film nobody had watched for an
|
|
9
|
-
* hour was indistinguishable from one being watched now.
|
|
10
|
-
*
|
|
11
|
-
* Two halves, and both are here: the side that builds the map has to say it,
|
|
12
|
-
* and the side that receives it has to act on it without needing the file's
|
|
13
|
-
* length, the torrent's list, or the torrent to exist at all.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import test from "node:test";
|
|
17
|
-
import assert from "node:assert/strict";
|
|
18
|
-
import { PriorityOrchestrator } from "../services/viewer/PriorityOrchestrator.js";
|
|
19
|
-
import { Viewers } from "../services/viewer/Viewers.js";
|
|
20
|
-
import { TorrentPool } from "../services/torrent/torrent-pool.js";
|
|
21
|
-
import { demandFor, forgetTorrent } from "../services/torrent/download/registry.js";
|
|
22
|
-
import { Urgency } from "../services/torrent/demand/index.js";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* A session, as much of one as the orchestrator reads.
|
|
26
|
-
*
|
|
27
|
-
* @param {{ id: string, fileIndex?: number }} params
|
|
28
|
-
* @returns {object}
|
|
29
|
-
*/
|
|
30
|
-
function outputOf({ id, fileIndex = 0 }) {
|
|
31
|
-
return {
|
|
32
|
-
id,
|
|
33
|
-
outputKey: `out:${id}`,
|
|
34
|
-
sourceKey: "source-1",
|
|
35
|
-
fileIndex,
|
|
36
|
-
file: { key: `film-${fileIndex}`, durationSeconds: 600 }
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The real orchestrator, with everything it publishes kept.
|
|
42
|
-
*
|
|
43
|
-
* @param {object[]} sessions
|
|
44
|
-
* @returns {{ priority: PriorityOrchestrator, viewers: Viewers, published: object[], publish: (sessions?: object[]) => void }}
|
|
45
|
-
*/
|
|
46
|
-
function over(sessions) {
|
|
47
|
-
const published = [];
|
|
48
|
-
const viewers = new Viewers();
|
|
49
|
-
const priority = new PriorityOrchestrator({
|
|
50
|
-
publish: (one) => published.push(one),
|
|
51
|
-
viewers,
|
|
52
|
-
allowanceFor: () => 10
|
|
53
|
-
});
|
|
54
|
-
return {
|
|
55
|
-
priority,
|
|
56
|
-
viewers,
|
|
57
|
-
published,
|
|
58
|
-
publish: (live = sessions) =>
|
|
59
|
-
priority.publishFor({ sessionGroups: [live] })
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
test("a file whose viewers have all gone is published as wanting nothing", () => {
|
|
64
|
-
const picture = outputOf({ id: "pic" });
|
|
65
|
-
const { viewers, published, publish } = over([picture]);
|
|
66
|
-
const person = viewers.of(picture, "p");
|
|
67
|
-
person.moveTo(300);
|
|
68
|
-
publish();
|
|
69
|
-
assert.ok(published.at(-1).zones.length > 0, "somebody is watching, so something is wanted");
|
|
70
|
-
|
|
71
|
-
// They leave. The session is still there — it outlives them by half an hour —
|
|
72
|
-
// so this is the case that used to say nothing at all.
|
|
73
|
-
viewers.leaves(picture, "p");
|
|
74
|
-
publish();
|
|
75
|
-
|
|
76
|
-
const last = published.at(-1);
|
|
77
|
-
assert.deepEqual(last.zones, []);
|
|
78
|
-
assert.equal(last.sourceKey, "source-1");
|
|
79
|
-
assert.equal(last.fileIndex, 0);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("a file whose session has gone is published as wanting nothing, once", () => {
|
|
83
|
-
const picture = outputOf({ id: "pic" });
|
|
84
|
-
const { viewers, published, publish } = over([picture]);
|
|
85
|
-
viewers.of(picture, "p").moveTo(300);
|
|
86
|
-
publish();
|
|
87
|
-
const said = published.length;
|
|
88
|
-
|
|
89
|
-
// The session is disposed: it is not among the live ones any more.
|
|
90
|
-
publish([]);
|
|
91
|
-
assert.deepEqual(published.at(-1).zones, []);
|
|
92
|
-
assert.equal(published.length, said + 1);
|
|
93
|
-
|
|
94
|
-
// And it is not repeated on every pass afterwards — the file is gone from
|
|
95
|
-
// this class's memory, and a departure is said once.
|
|
96
|
-
publish([]);
|
|
97
|
-
publish([]);
|
|
98
|
-
assert.equal(published.length, said + 1);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("an unchanged map is still not republished", () => {
|
|
102
|
-
const picture = outputOf({ id: "pic" });
|
|
103
|
-
const { viewers, published, publish } = over([picture]);
|
|
104
|
-
viewers.of(picture, "p").moveTo(300);
|
|
105
|
-
publish();
|
|
106
|
-
const said = published.length;
|
|
107
|
-
publish();
|
|
108
|
-
publish();
|
|
109
|
-
assert.equal(published.length, said, "the downloading rebuilds its requests on every one");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
/** A torrent that is nothing but one file of a known length. */
|
|
113
|
-
function torrentOf({ length = 1_000_000 } = {}) {
|
|
114
|
-
return {
|
|
115
|
-
infoHash: `hash-${Math.random().toString(36).slice(2)}`,
|
|
116
|
-
pieceLength: 1024,
|
|
117
|
-
files: [{ offset: 0, length, name: "film.mkv" }],
|
|
118
|
-
_selections: { _items: [] },
|
|
119
|
-
_select() {},
|
|
120
|
-
_deselect() {},
|
|
121
|
-
critical() {}
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const applyPriorityMap = TorrentPool.prototype.applyPriorityMap;
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @param {object} torrent
|
|
129
|
-
* @returns {object[]}
|
|
130
|
-
*/
|
|
131
|
-
function stated(torrent) {
|
|
132
|
-
return demandFor(torrent)
|
|
133
|
-
.register.windows()
|
|
134
|
-
.filter((one) => String(one.claimant).startsWith("priority-map:"));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
test("a map with nothing in it withdraws everything that file had stated", () => {
|
|
138
|
-
const torrent = torrentOf();
|
|
139
|
-
try {
|
|
140
|
-
applyPriorityMap.call(null, torrent, 0, [
|
|
141
|
-
{ from: 0, to: 100, priority: 100 },
|
|
142
|
-
{ from: 100, to: 600, priority: 50 }
|
|
143
|
-
], 600);
|
|
144
|
-
assert.equal(stated(torrent).length, 2);
|
|
145
|
-
|
|
146
|
-
applyPriorityMap.call(null, torrent, 0, [], 600);
|
|
147
|
-
assert.equal(stated(torrent).length, 0);
|
|
148
|
-
} finally {
|
|
149
|
-
forgetTorrent(torrent);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("it withdraws that file's bands and nobody else's", () => {
|
|
154
|
-
const torrent = torrentOf();
|
|
155
|
-
try {
|
|
156
|
-
const { register } = demandFor(torrent);
|
|
157
|
-
applyPriorityMap.call(null, torrent, 0, [{ from: 0, to: 600, priority: 100 }], 600);
|
|
158
|
-
// A read stopped on a piece right now, which only a read can say, and the
|
|
159
|
-
// background fill, which the pool states for itself. Neither is the map's
|
|
160
|
-
// to withdraw.
|
|
161
|
-
register.state({
|
|
162
|
-
claimant: "read-7:blocked",
|
|
163
|
-
fileIndex: 0,
|
|
164
|
-
byteStart: 0,
|
|
165
|
-
byteEnd: 999,
|
|
166
|
-
urgency: Urgency.BLOCKED
|
|
167
|
-
});
|
|
168
|
-
register.state({
|
|
169
|
-
claimant: "background-fill:0",
|
|
170
|
-
fileIndex: 0,
|
|
171
|
-
byteStart: 1000,
|
|
172
|
-
byteEnd: 9999,
|
|
173
|
-
urgency: Urgency.TAIL
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
applyPriorityMap.call(null, torrent, 0, [], 600);
|
|
177
|
-
|
|
178
|
-
assert.equal(stated(torrent).length, 0);
|
|
179
|
-
assert.deepEqual(
|
|
180
|
-
register.windows().map((one) => one.claimant).sort(),
|
|
181
|
-
["background-fill:0", "read-7:blocked"]
|
|
182
|
-
);
|
|
183
|
-
} finally {
|
|
184
|
-
forgetTorrent(torrent);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("a departure is answered even when nothing else about the file is known", () => {
|
|
189
|
-
// The length, the duration and the torrent's own list are what the ordinary
|
|
190
|
-
// path needs to turn seconds into bytes. A departure needs none of them, and
|
|
191
|
-
// it arrives exactly when they are going away: the map that says it is
|
|
192
|
-
// published as the last viewer leaves.
|
|
193
|
-
const torrent = torrentOf();
|
|
194
|
-
try {
|
|
195
|
-
applyPriorityMap.call(null, torrent, 0, [{ from: 0, to: 600, priority: 100 }], 600);
|
|
196
|
-
assert.equal(stated(torrent).length, 1);
|
|
197
|
-
|
|
198
|
-
torrent.files = [];
|
|
199
|
-
applyPriorityMap.call(null, torrent, 0, [], 0);
|
|
200
|
-
assert.equal(stated(torrent).length, 0);
|
|
201
|
-
} finally {
|
|
202
|
-
forgetTorrent(torrent);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One fact asked at two scopes: what the swarm is asked for, and what the
|
|
3
|
-
* encoders are placed by.
|
|
4
|
-
*
|
|
5
|
-
* Both are right for what asks them. The swarm is asked for bytes of a FILE,
|
|
6
|
-
* and the picture, a quality step and a soundtrack of one film read the same
|
|
7
|
-
* bytes — so every viewer of any of them wants that file's bytes. Encoders are
|
|
8
|
-
* placed per OUTPUT, and a person watching 480p wants nothing of the 1080p
|
|
9
|
-
* output at all.
|
|
10
|
-
*
|
|
11
|
-
* One map for both was the second authority over encoders. Every output of a
|
|
12
|
-
* film was handed the whole film's map, so the plan wanted an encoder on every
|
|
13
|
-
* one of them; what actually stopped the ones nobody was watching was the
|
|
14
|
-
* session manager killing them by its own judgement — and since a viewer moving
|
|
15
|
-
* between steps also announces itself, the plan started them again on the next
|
|
16
|
-
* pass. Field 2026-09-08.
|
|
17
|
-
*
|
|
18
|
-
* Built out of the real classes throughout: the real viewer registry holds real
|
|
19
|
-
* viewers, the real `OutputCatalog` answers what a film's shape is, and the real
|
|
20
|
-
* `PriorityOrchestrator` builds the maps. A session is a plain object in this
|
|
21
|
-
* proxy — there is no class for one — so the literals below are the thing
|
|
22
|
-
* itself and not a stand-in for it.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import test from "node:test";
|
|
26
|
-
import assert from "node:assert/strict";
|
|
27
|
-
import { PriorityOrchestrator } from "../services/viewer/PriorityOrchestrator.js";
|
|
28
|
-
import { OutputCatalog } from "../services/encode/output/OutputCatalog.js";
|
|
29
|
-
import { Viewers } from "../services/viewer/Viewers.js";
|
|
30
|
-
import { runsOf } from "../services/viewer/PriorityMap.js";
|
|
31
|
-
import { outputSpec } from "./helpers/output-spec.js";
|
|
32
|
-
|
|
33
|
-
const FILM = { sourceKey: "source-1", fileIndex: 0, durationSeconds: 600 };
|
|
34
|
-
const STALE_AFTER_MS = 60_000;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* A session, as much of one as these classes read.
|
|
38
|
-
*
|
|
39
|
-
* @param {{ id: string, outputKey: string, isStep?: boolean, audioOnly?: boolean }} params
|
|
40
|
-
* @returns {object}
|
|
41
|
-
*/
|
|
42
|
-
function outputOf({ id, outputKey, isStep = false, audioOnly = false }) {
|
|
43
|
-
return {
|
|
44
|
-
id,
|
|
45
|
-
spec: outputSpec({ sourceKey: FILM.sourceKey, audioOnly }),
|
|
46
|
-
outputKey,
|
|
47
|
-
isStep,
|
|
48
|
-
audioOnly,
|
|
49
|
-
state: "ready",
|
|
50
|
-
sourceKey: FILM.sourceKey,
|
|
51
|
-
fileIndex: FILM.fileIndex,
|
|
52
|
-
file: { key: "film-1", durationSeconds: FILM.durationSeconds }
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* The real orchestrator over the real registry, wired the way the session
|
|
58
|
-
* manager wires it.
|
|
59
|
-
*
|
|
60
|
-
* @param {object[]} sessions
|
|
61
|
-
* @returns {{ priority: PriorityOrchestrator, viewers: Viewers, publish: () => void }}
|
|
62
|
-
*/
|
|
63
|
-
function over(sessions) {
|
|
64
|
-
const live = new OutputCatalog();
|
|
65
|
-
for (const one of sessions) {
|
|
66
|
-
live.set(one.id, one);
|
|
67
|
-
if (one.isStep) live.markStep(one);
|
|
68
|
-
}
|
|
69
|
-
const viewers = new Viewers();
|
|
70
|
-
const priority = new PriorityOrchestrator({
|
|
71
|
-
publish: () => {},
|
|
72
|
-
viewers,
|
|
73
|
-
allowanceFor: () => 10,
|
|
74
|
-
watchedBy: (session, viewer) => !live.supersededBy(session, viewer.activeVariantId ?? null)
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
priority,
|
|
78
|
-
viewers,
|
|
79
|
-
live,
|
|
80
|
-
publish: () => priority.publishFor({ sessionGroups: [sessions], staleAfterMs: STALE_AFTER_MS })
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
test("a person on a step wants nothing of the picture they stepped off", () => {
|
|
85
|
-
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
86
|
-
const step = outputOf({ id: "step", outputKey: "out:480", isStep: true });
|
|
87
|
-
const { priority, viewers, publish } = over([picture, step]);
|
|
88
|
-
// ONE viewer object per person, referenced from both outputs — which is why
|
|
89
|
-
// the step they are on is known when the picture is asked about.
|
|
90
|
-
viewers.of(picture, "p");
|
|
91
|
-
const person = viewers.of(step, "p");
|
|
92
|
-
person.moveTo(300);
|
|
93
|
-
person.activeVariantId = "step";
|
|
94
|
-
publish();
|
|
95
|
-
|
|
96
|
-
assert.ok(
|
|
97
|
-
runsOf(priority.mapForOutput("out:480")).length > 0,
|
|
98
|
-
"the step they are watching is wanted"
|
|
99
|
-
);
|
|
100
|
-
assert.deepEqual(
|
|
101
|
-
runsOf(priority.mapForOutput("out:1080")),
|
|
102
|
-
[],
|
|
103
|
-
"and the picture is producing for nobody, which is what makes its encoder unwanted"
|
|
104
|
-
);
|
|
105
|
-
// The bytes are another matter: both outputs read the same file, and the
|
|
106
|
-
// swarm is asked for the file.
|
|
107
|
-
assert.ok(
|
|
108
|
-
runsOf(priority.mapFor(FILM.sourceKey, FILM.fileIndex)).length > 0,
|
|
109
|
-
"the download still wants the film, because that is what is being watched"
|
|
110
|
-
);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("a soundtrack is wanted by whoever is registered on it", () => {
|
|
114
|
-
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
115
|
-
const sound = outputOf({ id: "rus", outputKey: "out:a1", audioOnly: true });
|
|
116
|
-
const { priority, viewers, publish } = over([picture, sound]);
|
|
117
|
-
// Standing on a STEP of the picture says nothing about the sound: the tracks
|
|
118
|
-
// nobody chose are let go of where a track is chosen, so being known to a
|
|
119
|
-
// soundtrack is listening to it.
|
|
120
|
-
const person = viewers.of(sound, "p");
|
|
121
|
-
person.moveTo(120);
|
|
122
|
-
person.activeVariantId = "step";
|
|
123
|
-
publish();
|
|
124
|
-
|
|
125
|
-
assert.ok(runsOf(priority.mapForOutput("out:a1")).length > 0);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("a step being warmed is wanted, and so is the picture still on screen", () => {
|
|
129
|
-
// Both are genuinely being produced through a warm-up, which is the price of
|
|
130
|
-
// the switch not being visible. Whether the machine can afford two encoders
|
|
131
|
-
// is the budget's question and not this one's.
|
|
132
|
-
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
133
|
-
const step = outputOf({ id: "step", outputKey: "out:480", isStep: true });
|
|
134
|
-
const { priority, viewers, publish } = over([picture, step]);
|
|
135
|
-
viewers.of(picture, "p");
|
|
136
|
-
const person = viewers.of(step, "p");
|
|
137
|
-
person.moveTo(300);
|
|
138
|
-
person.warmingVariantId = "step";
|
|
139
|
-
publish();
|
|
140
|
-
|
|
141
|
-
assert.ok(runsOf(priority.mapForOutput("out:1080")).length > 0, "still on screen");
|
|
142
|
-
assert.ok(runsOf(priority.mapForOutput("out:480")).length > 0, "being made ready");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("an output everybody has left is stated as empty, not left as it was", () => {
|
|
146
|
-
// The difference matters: the plan stops the encoders of an output whose map
|
|
147
|
-
// is empty, and would keep the map it had when somebody was watching.
|
|
148
|
-
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
149
|
-
const { priority, viewers, publish } = over([picture]);
|
|
150
|
-
viewers.of(picture, "p").moveTo(300);
|
|
151
|
-
|
|
152
|
-
publish();
|
|
153
|
-
assert.ok(runsOf(priority.mapForOutput("out:1080")).length > 0);
|
|
154
|
-
|
|
155
|
-
viewers.leaves(picture, "p");
|
|
156
|
-
publish();
|
|
157
|
-
assert.deepEqual(runsOf(priority.mapForOutput("out:1080")), []);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("a viewer who has gone is not watching anything", () => {
|
|
161
|
-
// Presence is the connection and nothing else. Silence — however long — is a
|
|
162
|
-
// paused viewer, a throttled hidden tab, or a full cushion, and none of those
|
|
163
|
-
// is leaving; what ends it is a statement.
|
|
164
|
-
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
165
|
-
const live = new OutputCatalog();
|
|
166
|
-
live.set("pic", picture);
|
|
167
|
-
const viewers = new Viewers();
|
|
168
|
-
const priority = new PriorityOrchestrator({
|
|
169
|
-
publish: () => {},
|
|
170
|
-
viewers,
|
|
171
|
-
allowanceFor: () => 10,
|
|
172
|
-
watchedBy: (session, viewer) => !live.supersededBy(session, viewer.activeVariantId ?? null)
|
|
173
|
-
});
|
|
174
|
-
const person = viewers.of(picture, "p");
|
|
175
|
-
person.moveTo(300);
|
|
176
|
-
viewers.leaves(picture, "p");
|
|
177
|
-
|
|
178
|
-
priority.publishFor({ sessionGroups: [[picture]], now: Date.now() });
|
|
179
|
-
|
|
180
|
-
assert.deepEqual(runsOf(priority.mapForOutput("out:1080")), []);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("a file and an output that are gone are forgotten", () => {
|
|
184
|
-
// These are the projection of the live sessions, never a memory of them. Left
|
|
185
|
-
// to accumulate they were three maps that only grew, and `forget` was written
|
|
186
|
-
// for that and called from nowhere.
|
|
187
|
-
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
188
|
-
const { priority, viewers, publish } = over([picture]);
|
|
189
|
-
viewers.of(picture, "p").moveTo(300);
|
|
190
|
-
|
|
191
|
-
publish();
|
|
192
|
-
assert.ok(runsOf(priority.mapForOutput("out:1080")).length > 0);
|
|
193
|
-
|
|
194
|
-
priority.publishFor({ sessionGroups: [[]], staleAfterMs: STALE_AFTER_MS });
|
|
195
|
-
assert.deepEqual(runsOf(priority.mapForOutput("out:1080")), [], "the output is gone");
|
|
196
|
-
assert.deepEqual(
|
|
197
|
-
runsOf(priority.mapFor(FILM.sourceKey, FILM.fileIndex)),
|
|
198
|
-
[],
|
|
199
|
-
"and so is the file"
|
|
200
|
-
);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("the picture is watched by a person who never moved off it", () => {
|
|
204
|
-
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
205
|
-
const { viewers, live } = over([picture]);
|
|
206
|
-
const person = viewers.of(picture, "p");
|
|
207
|
-
|
|
208
|
-
assert.equal(live.supersededBy(picture, null), false, "no step is active");
|
|
209
|
-
person.activeVariantId = "pic";
|
|
210
|
-
assert.equal(
|
|
211
|
-
live.supersededBy(picture, person.activeVariantId),
|
|
212
|
-
false,
|
|
213
|
-
"and naming the picture itself as the step is the same statement"
|
|
214
|
-
);
|
|
215
|
-
});
|