@torrent-tv/proxy 2.87.4 → 2.87.6
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/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +8 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +94 -94
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +100 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +129 -129
- package/routes/api/transcode-sessions/post.js +160 -160
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +361 -361
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +332 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +755 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2162 -2162
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1083 -1083
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +266 -266
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +212 -212
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1146 -1146
- package/services/server/ViewerRequests.js +522 -522
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +36 -36
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +983 -983
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-pool.js +2 -2
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +1 -1
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/data-channel-handler.js +1356 -1356
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +657 -657
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -73
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +176 -176
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +35 -35
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +161 -161
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +381 -381
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +103 -103
- package/test/source-registry.test.js +18 -0
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +182 -182
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/{services/torrent → utils}/torrent-source-key.js +38 -38
|
@@ -1,215 +1,215 @@
|
|
|
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
|
-
});
|
|
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
|
+
});
|