@torrent-tv/proxy 2.89.6 → 2.89.7
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 +338 -338
- package/CHANGELOG.md +5 -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/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +765 -765
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +17 -2
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +273 -273
- 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/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- 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 +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- 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 +416 -416
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -95
- 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/server.js +748 -748
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1192 -1192
- package/services/encode/EncodePlan.js +1368 -1368
- package/services/encode/EncodeRun.js +32 -4
- package/services/encode/EncodeRuns.js +19 -7
- 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/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2283 -2283
- package/services/encode/RunClock.js +87 -87
- package/services/encode/RunProgress.js +79 -79
- 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 +418 -418
- 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 +116 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +43 -43
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- 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/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/piece-completeness.js +60 -2
- package/services/encode/quality/EncodeCost.js +1043 -1043
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +24 -24
- package/services/encode/quality/QualityController.js +758 -758
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +337 -337
- package/services/encode/quality/output-format.js +319 -319
- 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 +3 -1
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +53 -53
- package/services/encode/segment-formats/fmp4.js +476 -476
- package/services/encode/segment-formats/index.js +1 -0
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +8 -1
- 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/SourceFile.js +298 -298
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/container/KeyframeTable.js +272 -272
- 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/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- 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 +1152 -1152
- package/services/server/ViewerRequests.js +785 -785
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- 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 +1029 -1029
- 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 +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/returns.js +103 -103
- package/services/storage/wire.js +175 -175
- 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 +264 -264
- 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/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- 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/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- 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/media-time.js +355 -355
- package/services/viewer/playback-readiness.js +635 -635
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-buffer-continuity.test.js +77 -77
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- 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/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- 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 +275 -275
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +426 -426
- package/test/encode-plan-viewers.test.js +725 -725
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +38 -0
- 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 +68 -68
- 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/fixtures/refused-audio-20261002.json +10 -0
- package/test/fmp4-presentation-clock.test.js +4 -2
- 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/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +240 -240
- 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/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 +291 -291
- package/test/matroska-cues-track.test.js +192 -192
- package/test/media-time.test.js +62 -62
- 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-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +207 -207
- 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 +87 -87
- 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-completeness.test.js +37 -1
- package/test/piece-disk-store.test.js +401 -401
- 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 +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- 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/playback-readiness.test.js +518 -518
- package/test/playback-start-regressions.test.js +101 -101
- 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 +160 -160
- 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 +64 -64
- package/test/same-height-switch.test.js +663 -663
- 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-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-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +352 -352
- 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-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-registry.test.js +18 -18
- 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 +237 -237
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- 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/supply-margin.test.js +190 -190
- 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/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-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- 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/utils/torrent-source-key.js +38 -38
|
@@ -1,204 +1,204 @@
|
|
|
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
|
+
/**
|
|
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
|
+
});
|