@torrent-tv/proxy 2.86.0 → 2.87.1
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/CHANGELOG.md +2142 -1915
- package/CLAUDE.md +322 -197
- package/assets/calibration/NOTICE.md +2 -2
- package/bin/cli.js +684 -616
- package/biome.json +409 -233
- package/docs/browser-proxy-contract.md +94 -0
- package/docs/container-architecture.md +218 -213
- package/docs/download-architecture.md +7 -7
- package/docs/encode-architecture.md +287 -11
- package/docs/encode-run-state.md +2 -2
- package/docs/logs.md +97 -97
- package/docs/{disk-architecture.md → storage-architecture.md} +107 -1
- package/openspec/changes/adaptive-bitrate/design.md +2 -2
- package/openspec/changes/adaptive-bitrate/proposal.md +1 -1
- package/openspec/changes/adaptive-bitrate/tasks.md +1 -1
- package/openspec/changes/chunked-request-bodies/design.md +1 -1
- package/openspec/changes/chunked-request-bodies/proposal.md +1 -1
- package/openspec/changes/chunked-request-bodies/tasks.md +1 -1
- package/openspec/changes/cold-start/design.md +1 -1
- package/openspec/changes/disk-cap/proposal.md +1 -1
- package/openspec/changes/proxy-observability/proposal.md +2 -2
- package/openspec/changes/track-selection/proposal.md +1 -1
- package/openspec/changes/transcode-quality/proposal.md +1 -1
- package/package.json +2 -2
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/routes/api/playback-plan/post.js +2 -2
- package/routes/api/sources/files/get.js +1 -1
- package/routes/api/sources/stats/get.js +1 -1
- package/routes/api/sources/warm/post.js +10 -3
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +3 -3
- package/routes/api/transcode-sessions/net-report/post.js +129 -94
- package/routes/api/transcode-sessions/post.js +160 -139
- package/routes/api/transcode-sessions/progress/get.js +3 -3
- package/routes/api/transcode-sessions/release/post.js +3 -3
- package/routes/api/transcode-sessions/seek/post.js +12 -3
- package/routes/stream/get.js +2 -2
- package/routes/transcode/audio-file/get.js +15 -6
- package/routes/transcode/audio-warm/get.js +9 -6
- package/routes/transcode/session-file/get.js +52 -268
- package/routes/transcode/variant-file/get.js +46 -7
- package/routes/transcode/variant-warm/get.js +19 -6
- package/scripts/render-run-graph.js +1 -1
- package/server.js +720 -465
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -0
- package/services/encode/EncodeAdmission.js +332 -0
- package/services/{orchestrators → encode}/EncodeOrchestrator.js +307 -29
- package/services/encode/EncodeRun.js +16 -9
- package/services/encode/EncodeRuns.js +1229 -0
- package/services/encode/Encoder.js +18 -2
- package/services/encode/EncoderSelection.js +38 -0
- package/services/encode/LocalObservations.js +359 -0
- package/services/encode/NvencEncoder.js +5 -0
- package/services/encode/OutputOpening.js +755 -0
- package/services/encode/OutputTimes.js +579 -0
- package/services/encode/Renditions.js +2162 -0
- package/services/encode/RunProgress.js +84 -0
- package/services/encode/SoftwareEncoder.js +12 -7
- package/services/encode/args.js +402 -186
- package/services/encode/calibration.js +361 -0
- package/services/encode/contention.js +128 -128
- package/services/encode/encode-run-state.js +16 -20
- package/services/encode/fingerprint.js +322 -0
- package/services/{hwaccel.js → encode/hwaccel.js} +82 -42
- package/services/{learned-median.js → encode/learned-median.js} +11 -0
- package/services/encode/output/EncodedOutput.js +57 -0
- package/services/encode/output/Output.js +131 -0
- package/services/{output/LiveOutputs.js → encode/output/OutputCatalog.js} +141 -40
- package/services/encode/output/OutputSpec.js +390 -0
- package/services/{output → encode/output}/Timeline.js +12 -37
- package/services/encode/output/index.js +20 -0
- package/services/encode/output/ladder.js +32 -0
- package/services/{output → encode/output}/playlists.js +3 -0
- package/services/encode/output-key-format.js +21 -0
- package/services/encode/quality/EncodeCost.js +1083 -0
- package/services/encode/quality/HostLoad.js +525 -0
- package/services/encode/quality/HostTimings.js +249 -0
- package/services/encode/quality/OutputQualityState.js +28 -0
- package/services/encode/quality/QualityController.js +935 -0
- package/services/encode/quality/QualityOffer.js +416 -0
- package/services/encode/quality/drain-threat.js +47 -0
- package/services/encode/quality/link-budget.js +254 -0
- package/services/encode/quality/output-format.js +266 -0
- package/services/encode/quality/serving-output.js +216 -0
- package/services/encode/quality/visible-rung.js +57 -0
- package/services/encode/run-command.js +23 -20
- package/services/encode/run-inputs.js +42 -0
- package/services/{segment-formats → encode/segment-formats}/fmp4.js +63 -0
- package/services/{segment-formats → encode/segment-formats}/index.js +3 -3
- package/services/encode/segment-formats/init-compat.js +324 -0
- package/services/{segment-formats → encode/segment-formats}/mp4-boxes.js +8 -3
- package/services/encode/throughput.js +97 -0
- package/services/{orchestrators → media}/ContainerOrchestrator.js +45 -7
- package/services/media/KeyframeTables.js +316 -0
- package/services/{source → media}/SourceFile.js +7 -55
- package/services/{torrent-worker/subtitle-cues.js → media/SubtitleCues.js} +103 -132
- package/services/media/SubtitleOrchestrator.js +167 -0
- package/services/{audio-inventory.js → media/audio-inventory.js} +7 -1
- package/services/{container → media/container}/AviContainer.js +266 -266
- package/services/{container → media/container}/Container.js +596 -601
- package/services/media/container/ContainerFactory.js +56 -0
- package/services/media/container/KeyframeTable.js +272 -0
- package/services/{container → media/container}/MatroskaContainer.js +1485 -1485
- package/services/{container → media/container}/Mp4Container.js +1244 -1244
- package/services/{container → media/container}/SubtitleFileContainer.js +322 -322
- package/services/media/keyframe-probe.js +157 -0
- package/services/media/media-info-probe.js +93 -0
- package/services/{playback-planner.js → media/playback-planner.js} +82 -39
- package/services/{tracks → media/tracks}/AudioTrack.js +131 -131
- package/services/{tracks → media/tracks}/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -0
- package/services/server/SegmentServing.js +1146 -0
- package/services/server/ViewerRequests.js +522 -0
- package/services/server/controllers/PlaybackController.js +36 -0
- package/services/{controllers → server/controllers}/SubtitleController.js +17 -33
- package/services/server/http-responses.js +60 -0
- package/services/server/transcode-session-files.js +325 -0
- package/services/server/wire-outputs.js +983 -0
- package/services/storage/Diagnostics.js +202 -0
- package/services/storage/MachineBudget.js +235 -0
- package/services/{piece-store → storage}/allowance.js +107 -107
- package/services/{core-dumps.js → storage/core-dumps.js} +1 -1
- package/services/{files → storage/files}/CompletedFiles.js +116 -1
- package/services/{disk → storage}/free.js +33 -0
- package/services/storage/machine-memory.js +56 -0
- package/services/{memory-report.js → storage/memory-report.js} +575 -596
- package/services/{piece-store → storage/piece-store}/piece-disk-store.js +0 -7
- package/services/{piece-store → storage/piece-store}/shared-piece-store.js +28 -21
- package/services/{encode → storage/segment-store}/SegmentStore.js +395 -8
- package/services/storage/wire.js +175 -0
- package/services/{download → torrent/download}/SwarmSelection.js +2 -2
- package/services/{download → torrent/download}/registry.js +2 -2
- package/services/torrent/files.js +1 -1
- package/services/torrent/piece-store-of.js +29 -0
- package/services/{torrent-pool.js → torrent/torrent-pool.js} +26 -119
- package/services/{torrent-worker → torrent/worker}/background-fill.js +2 -2
- package/services/{torrent-worker → torrent/worker}/channel.js +271 -250
- package/services/{torrent-worker → torrent/worker}/client.js +77 -58
- package/services/torrent/worker/held-bytes.js +155 -0
- package/services/{torrent-worker → torrent/worker}/install-webrtc-shim.js +1 -1
- package/services/{torrent-worker → torrent/worker}/piece-reader.js +21 -30
- package/services/{torrent-worker → torrent/worker}/pool-adapter.js +101 -137
- package/services/{torrent-worker → torrent/worker}/protocol.js +26 -30
- package/services/torrent/worker/resume-warm.js +107 -0
- package/services/{torrent-worker → torrent/worker}/webrtc-shim.js +2 -2
- package/services/{torrent-worker → torrent/worker}/worker.js +68 -159
- package/services/{data-channel-handler.js → transport/data-channel-handler.js} +1356 -1356
- package/services/{delivery-probe.js → transport/delivery-probe.js} +842 -842
- package/services/{health-collector.js → transport/health-collector.js} +6 -35
- package/services/{packet-witness.js → transport/packet-witness.js} +808 -784
- package/services/{port-mapper.js → transport/port-mapper.js} +1 -1
- package/services/{usrsctp-state.js → transport/usrsctp-state.js} +1 -1
- package/services/{webrtc-manager.js → transport/webrtc-manager.js} +1 -1
- package/services/viewer/Assignments.js +340 -0
- package/services/{priority → viewer}/PriorityMap.js +390 -390
- package/services/{priority → viewer}/PriorityOrchestrator.js +325 -320
- package/services/viewer/Viewer.js +657 -483
- package/services/viewer/Viewers.js +368 -257
- package/services/viewer/active-output.js +37 -54
- package/services/viewer/audio-start.js +4 -3
- package/services/viewer/buffer-trend.js +81 -0
- package/services/viewer/choices.js +647 -0
- package/services/viewer/positions.js +126 -120
- package/services/viewer/report-intake.js +46 -45
- package/test/asset-paths.test.js +28 -0
- package/test/audio-inventory.test.js +3 -3
- package/test/audio-track-bitrate.test.js +40 -0
- package/test/auto-quality-step.test.js +707 -515
- package/test/available-share.test.js +1 -1
- package/test/background-fill.test.js +1 -1
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +1 -1
- package/test/concurrent-cost.test.js +13 -3
- package/test/container-media-info.test.js +3 -3
- package/test/core-dumps.test.js +1 -1
- package/test/coverage-follows-the-disk.test.js +46 -9
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -0
- package/test/cut-times-timeline.test.js +2 -2
- package/test/cuts-follow-published-grid.test.js +9 -8
- package/test/data-channel-frame.test.js +1 -1
- package/test/dead-torrent-handle.test.js +1 -1
- package/test/declared-rates.test.js +2 -2
- package/test/declared-tracks.test.js +2 -2
- package/test/decode-cost-fit.test.js +1 -1
- package/test/decode-cost.test.js +34 -25
- package/test/delivery-probe.test.js +1 -1
- package/test/demand-register.test.js +4 -4
- package/test/dht-bootstrap.test.js +1 -1
- package/test/drain-threat.test.js +65 -0
- package/test/duplicate-add.test.js +1 -1
- package/test/encode-admission.test.js +146 -0
- package/test/encode-cost.test.js +199 -10
- package/test/encode-orchestrator.test.js +39 -1
- package/test/encode-plan-viewers.test.js +2 -2
- package/test/encode-run.test.js +2 -2
- package/test/encode-slope.test.js +1 -1
- package/test/encoded-output.test.js +39 -0
- package/test/encoder-readings.test.js +1 -1
- package/test/every-claimant-is-told-its-share.test.js +152 -0
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -0
- package/test/failed-start-breaker.test.js +81 -30
- package/test/fastest-wires.test.js +1 -1
- package/test/ffmpeg-stream-counts.test.js +1 -1
- package/test/file-edges.test.js +3 -3
- package/test/fmp4-self-contained.test.js +1 -1
- package/test/fmp4-timestamps.test.js +1 -1
- package/test/fmp4-track-completeness.test.js +1 -1
- package/test/format-keyed-outputs.test.js +78 -0
- package/test/health-metrics.test.js +3 -2
- package/test/held-request-width.test.js +22 -23
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -0
- package/test/helpers/manager.js +57 -0
- package/test/helpers/output-spec.js +63 -0
- package/test/host-load.test.js +1 -1
- package/test/index-accuracy-is-about-the-file.test.js +128 -0
- package/test/init-compat.test.js +219 -0
- package/test/input-lost-quiets-the-plan.test.js +1 -1
- package/test/input-unavailable.test.js +1 -1
- package/test/keeping-period.test.js +4 -4
- package/test/keyframe-index-accuracy.test.js +97 -45
- package/test/keyframe-table-budget.test.js +28 -33
- package/test/keyframes-belong-to-the-file.test.js +174 -57
- package/test/learned-median.test.js +1 -1
- package/test/letting-go-asks-who-is-watching.test.js +191 -0
- package/test/limit-row-by-area.test.js +60 -0
- package/test/link-budget.test.js +111 -0
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -0
- package/test/look-ahead-coverage.test.js +1 -1
- package/test/manager-surface.test.js +294 -0
- package/test/matroska-blocks.test.js +0 -0
- package/test/matroska-cues-track.test.js +1 -1
- package/test/memory-budget.test.js +2 -2
- package/test/mp4-composition-times.test.js +0 -0
- package/test/mp4-subtitles.test.js +1 -1
- package/test/one-authority.test.js +45 -41
- package/test/one-budget.test.js +149 -0
- package/test/one-output-one-session.test.js +204 -0
- package/test/{disk-space.test.js → one-resource-divided.test.js} +30 -10
- package/test/one-serving-output-per-height.test.js +458 -0
- package/test/orchestrator-wired.test.js +46 -45
- package/test/output-catalog.test.js +39 -0
- package/test/output-shape.test.js +4 -51
- package/test/output-spec.test.js +251 -157
- package/test/output-speed.test.js +17 -15
- package/test/outputs-being-prepared.test.js +40 -0
- package/test/packet-witness-ring.test.js +2 -2
- package/test/packet-witness.test.js +1 -1
- package/test/piece-disk-store.test.js +2 -2
- package/test/piece-from-whole-file.test.js +1 -1
- package/test/piece-lru.test.js +1 -1
- package/test/piece-reader.test.js +2 -2
- package/test/piece-store-eviction.test.js +1 -1
- package/test/piece-store-never-refuses.test.js +1 -1
- package/test/piece-store-reservations.test.js +1 -1
- package/test/piece-store-slow-disk.test.js +1 -1
- package/test/piece-tail.test.js +1 -1
- package/test/plan-host-timings.test.js +1 -1
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +4 -4
- package/test/priority-map-emptied.test.js +204 -205
- package/test/priority-map-per-output.test.js +215 -209
- package/test/priority-map.test.js +1 -1
- package/test/probe-wedge-certainty.test.js +1 -1
- package/test/produced-copy-choice.test.js +15 -60
- package/test/quality-offer.test.js +162 -0
- package/test/quality-variants.test.js +1229 -1167
- package/test/rate-control-in-the-output.test.js +180 -0
- package/test/read-survives-withdrawal.test.js +2 -2
- package/test/read-window.test.js +10 -3
- package/test/resume-warm.test.js +1 -1
- package/test/run-inputs.test.js +80 -0
- package/test/run-position-follows-published-grid.test.js +13 -11
- package/test/run-progress.test.js +65 -0
- package/test/same-height-switch.test.js +664 -0
- package/test/seek-frees-the-viewers-requests.test.js +217 -0
- package/test/seek-landing.test.js +13 -13
- package/test/seek-target-not-superseded.test.js +28 -29
- package/test/segment-closed-on-both-branches.test.js +96 -0
- package/test/segment-cut-times.test.js +1 -1
- package/test/segment-hold-supersede.test.js +28 -35
- package/test/segment-judged-by-its-own-header.test.js +221 -0
- package/test/segment-serve-wiring.test.js +130 -39
- package/test/segment-store-eviction.test.js +22 -7
- package/test/segment-store.test.js +98 -14
- package/test/segment-true-start.test.js +1 -1
- package/test/segments-are-shared.test.js +17 -16
- package/test/send-chunk-bytes.test.js +1 -1
- package/test/serving-existing-output.test.js +102 -0
- package/test/serving-output.test.js +102 -0
- package/test/shared-piece-store.test.js +1 -1
- package/test/source-file.test.js +1 -1
- package/test/stale-request-after-seek.test.js +17 -21
- package/test/startup-readings.test.js +10 -5
- package/test/subtitle-cue-framing.test.js +5 -5
- package/test/subtitle-cue-source.test.js +36 -38
- package/test/subtitle-cue-walk.test.js +11 -10
- package/test/subtitle-defaults.test.js +1 -1
- package/test/subtitle-language.test.js +4 -4
- package/test/subtitle-track-numbering.test.js +5 -4
- package/test/supply-margin.test.js +1 -1
- package/test/swarm-follows-readers.test.js +3 -3
- package/test/swarm-reach.test.js +1 -1
- package/test/swarm-selection.test.js +3 -3
- package/test/tail-duplication.test.js +1 -1
- package/test/the-channel-has-both-ends.test.js +91 -0
- package/test/the-torrent-thread-serves-bytes.test.js +107 -0
- package/test/thread-pool.test.js +3 -3
- package/test/timeline.test.js +1 -12
- package/test/torrent-cost.test.js +1 -1
- package/test/tracks-begin-together.test.js +38 -24
- package/test/tunnel-renewal.test.js +1 -1
- package/test/two-viewers-one-picture.test.js +120 -87
- package/test/upload-hurry.test.js +3 -3
- package/test/usrsctp-state.test.js +1 -1
- package/test/video-facts.test.js +1 -1
- package/test/viewer-assignments.test.js +143 -0
- package/test/viewer-outputs.test.js +60 -52
- package/test/viewer-presence.test.js +22 -25
- package/test/viewer-requests-admission.test.js +89 -0
- package/test/viewer-states.test.js +189 -202
- package/test/viewer-subtitle-subscription.test.js +1 -1
- package/test/viewer.test.js +139 -136
- package/test/viewing-generations.test.js +390 -0
- package/test/visible-rung.test.js +37 -0
- package/test/wait-ledger.test.js +1 -1
- package/test/webrtc-shim.test.js +3 -3
- package/test/wedge-certainty.test.js +2 -2
- package/test/withdraw-piece-claim.test.js +2 -2
- package/test/worker-channel.test.js +1 -31
- package/utils/client-log-file.js +151 -151
- package/scripts/read-container-index.mjs +0 -108
- package/services/container/ContainerFactory.js +0 -122
- package/services/controllers/PlaybackController.js +0 -33
- package/services/disk/DiskSpace.js +0 -150
- package/services/disk/wire.js +0 -61
- package/services/hls-session-manager.js +0 -9608
- package/services/orchestrators/SubtitleOrchestrator.js +0 -160
- package/services/output/Output.js +0 -94
- package/services/output/OutputSpec.js +0 -202
- package/services/output/index.js +0 -1
- package/services/output/ladder.js +0 -26
- package/services/produced-index.js +0 -222
- package/services/quality/EncodeCost.js +0 -555
- package/services/torrent-worker/container-tracks.js +0 -414
- package/services/viewer/link-readings.js +0 -56
- package/test/produced-index.test.js +0 -142
- /package/{services → bin}/thread-pool.js +0 -0
- /package/services/{available-share.js → encode/available-share.js} +0 -0
- /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
- /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
- /package/services/{host-load.js → encode/host-load.js} +0 -0
- /package/services/{output → encode/output}/cut-grid.js +0 -0
- /package/services/{output → encode/output}/rates.js +0 -0
- /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
- /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
- /package/services/{container → media/container}/ebml-reader.js +0 -0
- /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
- /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
- /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
- /package/services/{tracks → media/tracks}/index.js +0 -0
- /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
- /package/services/{disk → storage}/keep.js +0 -0
- /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
- /package/services/{disk → storage}/returns.js +0 -0
- /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
- /package/services/{demand → torrent/demand}/Urgency.js +0 -0
- /package/services/{demand → torrent/demand}/Window.js +0 -0
- /package/services/{demand → torrent/demand}/index.js +0 -0
- /package/services/{demand → torrent/demand}/pieces.js +0 -0
- /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
- /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
- /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
- /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
- /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
- /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
- /package/services/{registry-api.js → transport/registry-api.js} +0 -0
- /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
- /package/services/{priority → viewer}/WaitLedger.js +0 -0
|
@@ -1,205 +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/
|
|
19
|
-
import { Viewers } from "../services/viewer/Viewers.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
id
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
fileIndex
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* @
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
assert.
|
|
79
|
-
assert.equal(last.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
assert.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
publish([]);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
publish();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* @
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
{ from:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
assert.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
});
|
|
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
|
+
});
|