@torrent-tv/proxy 2.88.3 → 2.88.4
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 +2259 -2250
- 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 +23 -4
- 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/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 -173
- 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 +380 -380
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -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/server.js +725 -725
- 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 +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +813 -813
- package/services/encode/EncodeRuns.js +1235 -1235
- 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 +783 -758
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2282 -2184
- 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 +418 -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 +1150 -1141
- 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 +349 -254
- package/services/encode/quality/output-format.js +319 -315
- 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 +754 -754
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +456 -456
- package/services/encode/segment-formats/index.js +85 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- 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 +298 -245
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +609 -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 +1344 -1244
- 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 -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/audio-bound.js +233 -0
- 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 +1150 -1150
- package/services/server/ViewerRequests.js +760 -760
- 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 +995 -992
- 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/piece-store/shared-piece-store.js +1931 -1931
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1240 -1240
- 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/episode-naming.js +182 -182
- 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 +2675 -2675
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -765
- 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 +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/torrent/worker/worker.js +1129 -1129
- package/services/transport/data-channel-handler.js +1369 -1369
- 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 +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/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 +70 -70
- package/test/audio-inventory.test.js +212 -212
- 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/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/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 +162 -162
- 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/episode-naming.test.js +131 -131
- 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/audio-frame-boundary.json +1 -1
- package/test/fmp4-presentation-clock.test.js +147 -147
- 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 +112 -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 +270 -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 +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 +453 -453
- package/test/playback-start-regressions.test.js +99 -99
- 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 +1257 -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 +105 -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/soundtrack-rate-bound.test.js +289 -0
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +113 -113
- 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 +209 -209
- 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 +504 -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/utils/torrent-source-key.js +38 -38
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One demand register and one swarm selection per torrent, found from the
|
|
3
|
-
* torrent itself.
|
|
4
|
-
*
|
|
5
|
-
* The same shape the piece store already uses — `pieceStoreOf(torrent)`
|
|
6
|
-
* reaches the store without it being threaded through every call — and for the
|
|
7
|
-
* same reason: the reader, the pool and the background fill all need the same
|
|
8
|
-
* instance, and passing it through six layers of arguments would make the
|
|
9
|
-
* argument lists bigger than the thing they carry.
|
|
10
|
-
*
|
|
11
|
-
* Kept in a live set as well as a weak map, because one question cannot be
|
|
12
|
-
* answered per torrent: whether ANYTHING anywhere is still missing something
|
|
13
|
-
* urgent. The link and the machine are shared between torrents, so a viewer
|
|
14
|
-
* starving on one film must stop the speculative fetching on the other. Asked
|
|
15
|
-
* per torrent, that question has the wrong answer.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { DemandRegister } from "../demand/DemandRegister.js";
|
|
19
|
-
import { SwarmSelection } from "./SwarmSelection.js";
|
|
20
|
-
|
|
21
|
-
/** @type {WeakMap<object, { register: DemandRegister, selection: SwarmSelection }>} */
|
|
22
|
-
const byTorrent = new WeakMap();
|
|
23
|
-
/** @type {Set<{ register: DemandRegister, selection: SwarmSelection }>} */
|
|
24
|
-
const live = new Set();
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The register and selection for a torrent, made on first use.
|
|
28
|
-
*
|
|
29
|
-
* @param {object} torrent
|
|
30
|
-
* @returns {{ register: DemandRegister, selection: SwarmSelection }}
|
|
31
|
-
*/
|
|
32
|
-
export function demandFor(torrent) {
|
|
33
|
-
const held = byTorrent.get(torrent);
|
|
34
|
-
if (held) {
|
|
35
|
-
return held;
|
|
36
|
-
}
|
|
37
|
-
const register = new DemandRegister();
|
|
38
|
-
const entry = { register, selection: new SwarmSelection({ torrent, register }) };
|
|
39
|
-
byTorrent.set(torrent, entry);
|
|
40
|
-
live.add(entry);
|
|
41
|
-
return entry;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Whether this torrent is still short of anything anybody asked for.
|
|
46
|
-
*
|
|
47
|
-
* Read by the upload policy: while a reader is missing bytes, a little upload
|
|
48
|
-
* buys the reciprocity that gets them; once nothing declared is missing it buys
|
|
49
|
-
* nothing at all, and on 2026-09-11 the proxy went on offering 512 KB/s of a
|
|
50
|
-
* fully downloaded file to 596 peers for forty-eight minutes, reading a 4 MB
|
|
51
|
-
* piece off the disk for every 16 KB it sent.
|
|
52
|
-
*
|
|
53
|
-
* False for a torrent nothing has been stated about, which is the same answer
|
|
54
|
-
* and the right one: nobody is waiting for it.
|
|
55
|
-
*
|
|
56
|
-
* @param {object} torrent
|
|
57
|
-
* @returns {boolean}
|
|
58
|
-
*/
|
|
59
|
-
export function hasUnmetDemand(torrent) {
|
|
60
|
-
const held = byTorrent.get(torrent);
|
|
61
|
-
return held ? held.selection.hasUrgentMissing() : false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Give up everything stated for a torrent that is going.
|
|
66
|
-
*
|
|
67
|
-
* @param {object} torrent
|
|
68
|
-
* @returns {void}
|
|
69
|
-
*/
|
|
70
|
-
export function forgetTorrent(torrent) {
|
|
71
|
-
const held = byTorrent.get(torrent);
|
|
72
|
-
if (!held) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
held.selection.releaseAll();
|
|
76
|
-
held.register.clear();
|
|
77
|
-
byTorrent.delete(torrent);
|
|
78
|
-
live.delete(held);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Bring every torrent's download set into line with what is stated.
|
|
83
|
-
*
|
|
84
|
-
* The cross-torrent rule lives here and not in a selection, because it is not a
|
|
85
|
-
* per-torrent question: two films on one proxy share the link, so filling the
|
|
86
|
-
* tail of one while a viewer of the other has a still picture spends the same
|
|
87
|
-
* bandwidth twice over. The answer is worked out once and given to all.
|
|
88
|
-
*
|
|
89
|
-
* @returns {{ torrents: number, speculativeAllowed: boolean, stated: number, withdrawn: number }}
|
|
90
|
-
*/
|
|
91
|
-
export function reconcileAll() {
|
|
92
|
-
const entries = [...live];
|
|
93
|
-
const speculativeAllowed = !entries.some((entry) => entry.selection.hasUrgentMissing());
|
|
94
|
-
let stated = 0;
|
|
95
|
-
let withdrawn = 0;
|
|
96
|
-
for (const entry of entries) {
|
|
97
|
-
const result = entry.selection.reconcile({ speculativeAllowed });
|
|
98
|
-
stated += result.stated;
|
|
99
|
-
withdrawn += result.withdrawn;
|
|
100
|
-
}
|
|
101
|
-
return { torrents: entries.length, speculativeAllowed, stated, withdrawn };
|
|
102
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file One demand register and one swarm selection per torrent, found from the
|
|
3
|
+
* torrent itself.
|
|
4
|
+
*
|
|
5
|
+
* The same shape the piece store already uses — `pieceStoreOf(torrent)`
|
|
6
|
+
* reaches the store without it being threaded through every call — and for the
|
|
7
|
+
* same reason: the reader, the pool and the background fill all need the same
|
|
8
|
+
* instance, and passing it through six layers of arguments would make the
|
|
9
|
+
* argument lists bigger than the thing they carry.
|
|
10
|
+
*
|
|
11
|
+
* Kept in a live set as well as a weak map, because one question cannot be
|
|
12
|
+
* answered per torrent: whether ANYTHING anywhere is still missing something
|
|
13
|
+
* urgent. The link and the machine are shared between torrents, so a viewer
|
|
14
|
+
* starving on one film must stop the speculative fetching on the other. Asked
|
|
15
|
+
* per torrent, that question has the wrong answer.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { DemandRegister } from "../demand/DemandRegister.js";
|
|
19
|
+
import { SwarmSelection } from "./SwarmSelection.js";
|
|
20
|
+
|
|
21
|
+
/** @type {WeakMap<object, { register: DemandRegister, selection: SwarmSelection }>} */
|
|
22
|
+
const byTorrent = new WeakMap();
|
|
23
|
+
/** @type {Set<{ register: DemandRegister, selection: SwarmSelection }>} */
|
|
24
|
+
const live = new Set();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The register and selection for a torrent, made on first use.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} torrent
|
|
30
|
+
* @returns {{ register: DemandRegister, selection: SwarmSelection }}
|
|
31
|
+
*/
|
|
32
|
+
export function demandFor(torrent) {
|
|
33
|
+
const held = byTorrent.get(torrent);
|
|
34
|
+
if (held) {
|
|
35
|
+
return held;
|
|
36
|
+
}
|
|
37
|
+
const register = new DemandRegister();
|
|
38
|
+
const entry = { register, selection: new SwarmSelection({ torrent, register }) };
|
|
39
|
+
byTorrent.set(torrent, entry);
|
|
40
|
+
live.add(entry);
|
|
41
|
+
return entry;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether this torrent is still short of anything anybody asked for.
|
|
46
|
+
*
|
|
47
|
+
* Read by the upload policy: while a reader is missing bytes, a little upload
|
|
48
|
+
* buys the reciprocity that gets them; once nothing declared is missing it buys
|
|
49
|
+
* nothing at all, and on 2026-09-11 the proxy went on offering 512 KB/s of a
|
|
50
|
+
* fully downloaded file to 596 peers for forty-eight minutes, reading a 4 MB
|
|
51
|
+
* piece off the disk for every 16 KB it sent.
|
|
52
|
+
*
|
|
53
|
+
* False for a torrent nothing has been stated about, which is the same answer
|
|
54
|
+
* and the right one: nobody is waiting for it.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} torrent
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
export function hasUnmetDemand(torrent) {
|
|
60
|
+
const held = byTorrent.get(torrent);
|
|
61
|
+
return held ? held.selection.hasUrgentMissing() : false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Give up everything stated for a torrent that is going.
|
|
66
|
+
*
|
|
67
|
+
* @param {object} torrent
|
|
68
|
+
* @returns {void}
|
|
69
|
+
*/
|
|
70
|
+
export function forgetTorrent(torrent) {
|
|
71
|
+
const held = byTorrent.get(torrent);
|
|
72
|
+
if (!held) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
held.selection.releaseAll();
|
|
76
|
+
held.register.clear();
|
|
77
|
+
byTorrent.delete(torrent);
|
|
78
|
+
live.delete(held);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Bring every torrent's download set into line with what is stated.
|
|
83
|
+
*
|
|
84
|
+
* The cross-torrent rule lives here and not in a selection, because it is not a
|
|
85
|
+
* per-torrent question: two films on one proxy share the link, so filling the
|
|
86
|
+
* tail of one while a viewer of the other has a still picture spends the same
|
|
87
|
+
* bandwidth twice over. The answer is worked out once and given to all.
|
|
88
|
+
*
|
|
89
|
+
* @returns {{ torrents: number, speculativeAllowed: boolean, stated: number, withdrawn: number }}
|
|
90
|
+
*/
|
|
91
|
+
export function reconcileAll() {
|
|
92
|
+
const entries = [...live];
|
|
93
|
+
const speculativeAllowed = !entries.some((entry) => entry.selection.hasUrgentMissing());
|
|
94
|
+
let stated = 0;
|
|
95
|
+
let withdrawn = 0;
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const result = entry.selection.reconcile({ speculativeAllowed });
|
|
98
|
+
stated += result.stated;
|
|
99
|
+
withdrawn += result.withdrawn;
|
|
100
|
+
}
|
|
101
|
+
return { torrents: entries.length, speculativeAllowed, stated, withdrawn };
|
|
102
|
+
}
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Withdrawing the claim that this proxy has a piece.
|
|
3
|
-
*
|
|
4
|
-
* **The fact has one owner and it is the store.** The store holds the bytes, so
|
|
5
|
-
* it says what this proxy has. The library keeps a second copy of that fact in
|
|
6
|
-
* its completion bitfield, and until 2026-09-12 nothing reconciled the two: the
|
|
7
|
-
* disk tier drops a piece once every reader is past it — correctly, and that is
|
|
8
|
-
* what bounds the spill — while the bitfield went on saying the piece was
|
|
9
|
-
* verified. A read then concluded the piece was had, asked for it, was told it
|
|
10
|
-
* was absent, and failed. Nor was it ever fetched again, because the library
|
|
11
|
-
* does not download what it believes it already owns.
|
|
12
|
-
*
|
|
13
|
-
* Field 2026-09-12: a film played 80 seconds; the encoder ran on to 725 s, so
|
|
14
|
-
* some 565 spilled pieces fell behind every read head and were dropped,
|
|
15
|
-
* including piece 0; the encoder then lost its input and restarted, which
|
|
16
|
-
* re-opens the input at byte 0; and `/stream` answered `0 of 2363497962 bytes:
|
|
17
|
-
* Piece 0 is verified but absent from the store` to every read for the next 92
|
|
18
|
-
* minutes while the browser retried one segment and the picture stood still.
|
|
19
|
-
*
|
|
20
|
-
* So the eviction's own bargain — "a seek back re-downloads it" — is made true
|
|
21
|
-
* here, and the bitfield becomes a projection of what the store holds.
|
|
22
|
-
*
|
|
23
|
-
* **Why it is a plain function.** It takes the values it needs and holds
|
|
24
|
-
* nothing: no pool, no store, no client. That is what lets it be exercised with
|
|
25
|
-
* an object literal, and it is the rule the layers are checked against — a layer
|
|
26
|
-
* must be usable with plain values alone, with no process, no disk and no clock.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Tell the library it no longer has a piece, so the next read of it waits for a
|
|
31
|
-
* download instead of failing.
|
|
32
|
-
*
|
|
33
|
-
* **What it deliberately does not do.** The library's `_markUnverified` would
|
|
34
|
-
* also re-select the piece; it does not here, because every torrent is added
|
|
35
|
-
* with `deselect: true`, which sets the library's own `_startAsDeselected` and
|
|
36
|
-
* makes it skip that call. The download set has exactly one owner —
|
|
37
|
-
* `SwarmSelection`, from the priority map — and a withdrawn piece is fetched
|
|
38
|
-
* again when a read states it, which is the same statement every other piece
|
|
39
|
-
* waits on.
|
|
40
|
-
*
|
|
41
|
-
* @param {object} what
|
|
42
|
-
* @param {number} what.index - The piece the store can no longer produce.
|
|
43
|
-
* @param {object[]} [what.files] - The store's own files; the torrent that owns
|
|
44
|
-
* them is found through one of them, because the store has no idea what a
|
|
45
|
-
* torrent is.
|
|
46
|
-
* @param {object} [what.torrent] - Given directly instead of through `files`.
|
|
47
|
-
* @param {(line: string) => void} [what.warn] - Said when the library refuses.
|
|
48
|
-
* @returns {"withdrawn" | "nothing-to-withdraw" | "no-torrent" | "refused"}
|
|
49
|
-
*/
|
|
50
|
-
export function withdrawClaim({ index, files, torrent, warn = () => undefined }) {
|
|
51
|
-
if (!Number.isInteger(index) || index < 0) {
|
|
52
|
-
return "nothing-to-withdraw";
|
|
53
|
-
}
|
|
54
|
-
const owner = torrent ?? files?.[0]?._torrent ?? null;
|
|
55
|
-
// A torrent being destroyed is the ordinary case at the end of a session, and
|
|
56
|
-
// it has no claim left to withdraw: the store is going with it.
|
|
57
|
-
if (!owner || owner.destroyed || typeof owner._markUnverified !== "function") {
|
|
58
|
-
return "no-torrent";
|
|
59
|
-
}
|
|
60
|
-
// NOTHING TO WITHDRAW, which is most of the time: the store also drops pieces
|
|
61
|
-
// it never completed, and marking one unverified that the library already
|
|
62
|
-
// knows is missing would re-create the piece and discard whatever blocks are
|
|
63
|
-
// in flight for it.
|
|
64
|
-
if (!owner.bitfield?.get?.(index)) {
|
|
65
|
-
return "nothing-to-withdraw";
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
owner._markUnverified(index);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
// This reaches into the library's own bookkeeping. If a later version
|
|
71
|
-
// changes it, the honest result is a line saying so rather than an eviction
|
|
72
|
-
// that fails.
|
|
73
|
-
warn(
|
|
74
|
-
`could not withdraw the claim on piece ${index} of ${owner.name ?? "?"}: ` +
|
|
75
|
-
`${error?.message ?? error}`
|
|
76
|
-
);
|
|
77
|
-
return "refused";
|
|
78
|
-
}
|
|
79
|
-
return "withdrawn";
|
|
80
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file Withdrawing the claim that this proxy has a piece.
|
|
3
|
+
*
|
|
4
|
+
* **The fact has one owner and it is the store.** The store holds the bytes, so
|
|
5
|
+
* it says what this proxy has. The library keeps a second copy of that fact in
|
|
6
|
+
* its completion bitfield, and until 2026-09-12 nothing reconciled the two: the
|
|
7
|
+
* disk tier drops a piece once every reader is past it — correctly, and that is
|
|
8
|
+
* what bounds the spill — while the bitfield went on saying the piece was
|
|
9
|
+
* verified. A read then concluded the piece was had, asked for it, was told it
|
|
10
|
+
* was absent, and failed. Nor was it ever fetched again, because the library
|
|
11
|
+
* does not download what it believes it already owns.
|
|
12
|
+
*
|
|
13
|
+
* Field 2026-09-12: a film played 80 seconds; the encoder ran on to 725 s, so
|
|
14
|
+
* some 565 spilled pieces fell behind every read head and were dropped,
|
|
15
|
+
* including piece 0; the encoder then lost its input and restarted, which
|
|
16
|
+
* re-opens the input at byte 0; and `/stream` answered `0 of 2363497962 bytes:
|
|
17
|
+
* Piece 0 is verified but absent from the store` to every read for the next 92
|
|
18
|
+
* minutes while the browser retried one segment and the picture stood still.
|
|
19
|
+
*
|
|
20
|
+
* So the eviction's own bargain — "a seek back re-downloads it" — is made true
|
|
21
|
+
* here, and the bitfield becomes a projection of what the store holds.
|
|
22
|
+
*
|
|
23
|
+
* **Why it is a plain function.** It takes the values it needs and holds
|
|
24
|
+
* nothing: no pool, no store, no client. That is what lets it be exercised with
|
|
25
|
+
* an object literal, and it is the rule the layers are checked against — a layer
|
|
26
|
+
* must be usable with plain values alone, with no process, no disk and no clock.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Tell the library it no longer has a piece, so the next read of it waits for a
|
|
31
|
+
* download instead of failing.
|
|
32
|
+
*
|
|
33
|
+
* **What it deliberately does not do.** The library's `_markUnverified` would
|
|
34
|
+
* also re-select the piece; it does not here, because every torrent is added
|
|
35
|
+
* with `deselect: true`, which sets the library's own `_startAsDeselected` and
|
|
36
|
+
* makes it skip that call. The download set has exactly one owner —
|
|
37
|
+
* `SwarmSelection`, from the priority map — and a withdrawn piece is fetched
|
|
38
|
+
* again when a read states it, which is the same statement every other piece
|
|
39
|
+
* waits on.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} what
|
|
42
|
+
* @param {number} what.index - The piece the store can no longer produce.
|
|
43
|
+
* @param {object[]} [what.files] - The store's own files; the torrent that owns
|
|
44
|
+
* them is found through one of them, because the store has no idea what a
|
|
45
|
+
* torrent is.
|
|
46
|
+
* @param {object} [what.torrent] - Given directly instead of through `files`.
|
|
47
|
+
* @param {(line: string) => void} [what.warn] - Said when the library refuses.
|
|
48
|
+
* @returns {"withdrawn" | "nothing-to-withdraw" | "no-torrent" | "refused"}
|
|
49
|
+
*/
|
|
50
|
+
export function withdrawClaim({ index, files, torrent, warn = () => undefined }) {
|
|
51
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
52
|
+
return "nothing-to-withdraw";
|
|
53
|
+
}
|
|
54
|
+
const owner = torrent ?? files?.[0]?._torrent ?? null;
|
|
55
|
+
// A torrent being destroyed is the ordinary case at the end of a session, and
|
|
56
|
+
// it has no claim left to withdraw: the store is going with it.
|
|
57
|
+
if (!owner || owner.destroyed || typeof owner._markUnverified !== "function") {
|
|
58
|
+
return "no-torrent";
|
|
59
|
+
}
|
|
60
|
+
// NOTHING TO WITHDRAW, which is most of the time: the store also drops pieces
|
|
61
|
+
// it never completed, and marking one unverified that the library already
|
|
62
|
+
// knows is missing would re-create the piece and discard whatever blocks are
|
|
63
|
+
// in flight for it.
|
|
64
|
+
if (!owner.bitfield?.get?.(index)) {
|
|
65
|
+
return "nothing-to-withdraw";
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
owner._markUnverified(index);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// This reaches into the library's own bookkeeping. If a later version
|
|
71
|
+
// changes it, the honest result is a line saying so rather than an eviction
|
|
72
|
+
// that fails.
|
|
73
|
+
warn(
|
|
74
|
+
`could not withdraw the claim on piece ${index} of ${owner.name ?? "?"}: ` +
|
|
75
|
+
`${error?.message ?? error}`
|
|
76
|
+
);
|
|
77
|
+
return "refused";
|
|
78
|
+
}
|
|
79
|
+
return "withdrawn";
|
|
80
|
+
}
|