@torrent-tv/proxy 2.87.6 → 2.87.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 +322 -322
- package/CHANGELOG.md +4 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +101 -94
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -0
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +100 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +129 -129
- package/routes/api/transcode-sessions/post.js +160 -160
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +361 -361
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/server.js +4 -0
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +332 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +755 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2162 -2162
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1083 -1083
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +266 -266
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +245 -212
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +131 -59
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1146 -1146
- package/services/server/ViewerRequests.js +522 -522
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -36
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +983 -983
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/data-channel-handler.js +1356 -1356
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +657 -657
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +212 -176
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +35 -35
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +161 -161
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +381 -381
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +103 -103
- package/test/source-registry.test.js +18 -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 +182 -182
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file How urgent a stated need is, and what follows from that.
|
|
3
|
-
*
|
|
4
|
-
* Five levels, ordered. The ordering is not a set of numbers handed to
|
|
5
|
-
* WebTorrent — measured 2026-09-02, the library does not keep them. Its picker
|
|
6
|
-
* sorts selections by priority only at the moment one is inserted, and then, on
|
|
7
|
-
* every wire it fills, moves the selection it just served to the BACK of the
|
|
8
|
-
* whole group with a non-zero priority:
|
|
9
|
-
*
|
|
10
|
-
* ```js
|
|
11
|
-
* function shufflePriority (i) {
|
|
12
|
-
* let last = i
|
|
13
|
-
* for (let j = i; j < self._selections.length && self._selections.get(j).priority; j++) last = j
|
|
14
|
-
* self._selections.swap(i, last)
|
|
15
|
-
* }
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* So distinct non-zero numbers give an order once and a round robin thereafter.
|
|
19
|
-
* Only two things hold: non-zero rotates fairly, zero is always last.
|
|
20
|
-
*
|
|
21
|
-
* Therefore urgency here is expressed by WHETHER a need is stated to the swarm
|
|
22
|
-
* at all. A level is stated only while every level above it is satisfied, and
|
|
23
|
-
* withdrawn the moment one is not — a withdrawn need is not in the download set
|
|
24
|
-
* at all, so a peer with nothing urgent to give cannot fall through to it and
|
|
25
|
-
* spend the shared link on it. A permanently low priority does exactly that,
|
|
26
|
-
* which is why it is not what this does.
|
|
27
|
-
*
|
|
28
|
-
* The rotation is not merely tolerated, it is wanted: with two viewers of one
|
|
29
|
-
* film both stopped, both their needs sit at {@link Urgency.BLOCKED} and the
|
|
30
|
-
* swarm alternates between them instead of always serving whoever asked first.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The levels, most urgent first. The value is the position, so `<` compares
|
|
35
|
-
* urgency and nothing else may be read into it.
|
|
36
|
-
*
|
|
37
|
-
* @enum {number}
|
|
38
|
-
*/
|
|
39
|
-
export const Urgency = {
|
|
40
|
-
/**
|
|
41
|
-
* The bytes a reader is stopped on. The picture is not moving until they
|
|
42
|
-
* arrive.
|
|
43
|
-
*/
|
|
44
|
-
BLOCKED: 0,
|
|
45
|
-
/**
|
|
46
|
-
* The rest of that reader's own window — the cushion being built. Late here
|
|
47
|
-
* and the picture stops in a few seconds.
|
|
48
|
-
*/
|
|
49
|
-
NEAR: 1,
|
|
50
|
-
/**
|
|
51
|
-
* Further along the file, as far as the encoder will reach. Late here costs
|
|
52
|
-
* nothing yet.
|
|
53
|
-
*/
|
|
54
|
-
AHEAD: 2,
|
|
55
|
-
/**
|
|
56
|
-
* To the end of the file. Wanted for certain if the viewer watches on, and
|
|
57
|
-
* not before anything above is satisfied.
|
|
58
|
-
*/
|
|
59
|
-
TAIL: 3,
|
|
60
|
-
/**
|
|
61
|
-
* The gap behind the playhead, left by a forward seek. Wanted only if the
|
|
62
|
-
* viewer seeks back, so it is last — but it is real: a backward seek into a
|
|
63
|
-
* hole is the longest wait this proxy produces (field: 93 s).
|
|
64
|
-
*/
|
|
65
|
-
BEHIND: 4
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/** Every level, most urgent first. */
|
|
69
|
-
export const URGENCY_ORDER = [
|
|
70
|
-
Urgency.BLOCKED,
|
|
71
|
-
Urgency.NEAR,
|
|
72
|
-
Urgency.AHEAD,
|
|
73
|
-
Urgency.TAIL,
|
|
74
|
-
Urgency.BEHIND
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Whether a level may take a block away from a slow peer and give it to a fast
|
|
79
|
-
* one — WebTorrent's `critical`.
|
|
80
|
-
*
|
|
81
|
-
* Only the level that is being waited on. Displacement throws away the part of
|
|
82
|
-
* the block the slow peer had already fetched, so it buys time exactly where
|
|
83
|
-
* time is what is short, and wastes bandwidth everywhere else.
|
|
84
|
-
*
|
|
85
|
-
* @param {number} urgency
|
|
86
|
-
* @returns {boolean}
|
|
87
|
-
*/
|
|
88
|
-
export function mayDisplaceSlowPeer(urgency) {
|
|
89
|
-
return urgency === Urgency.BLOCKED;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Whether a level is stated to the swarm only while the levels above it want
|
|
94
|
-
* nothing.
|
|
95
|
-
*
|
|
96
|
-
* The three urgent levels are always stated: they are what the viewer is
|
|
97
|
-
* waiting for. The two speculative ones are stated only in the room the urgent
|
|
98
|
-
* ones leave.
|
|
99
|
-
*
|
|
100
|
-
* @param {number} urgency
|
|
101
|
-
* @returns {boolean}
|
|
102
|
-
*/
|
|
103
|
-
export function isConditional(urgency) {
|
|
104
|
-
return urgency >= Urgency.TAIL;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* The number handed to `torrent.select`.
|
|
109
|
-
*
|
|
110
|
-
* Two values only, because two is all the library keeps: everything urgent
|
|
111
|
-
* shares one non-zero priority and rotates fairly among itself; the
|
|
112
|
-
* speculative levels take zero, which the library always places last and never
|
|
113
|
-
* rotates into the group above.
|
|
114
|
-
*
|
|
115
|
-
* @param {number} urgency
|
|
116
|
-
* @returns {number}
|
|
117
|
-
*/
|
|
118
|
-
export function selectionPriority(urgency) {
|
|
119
|
-
return isConditional(urgency) ? 0 : 1;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* A level's name, for the log.
|
|
124
|
-
*
|
|
125
|
-
* @param {number} urgency
|
|
126
|
-
* @returns {string}
|
|
127
|
-
*/
|
|
128
|
-
export function urgencyName(urgency) {
|
|
129
|
-
switch (urgency) {
|
|
130
|
-
case Urgency.BLOCKED: return "blocked";
|
|
131
|
-
case Urgency.NEAR: return "near";
|
|
132
|
-
case Urgency.AHEAD: return "ahead";
|
|
133
|
-
case Urgency.TAIL: return "tail";
|
|
134
|
-
case Urgency.BEHIND: return "behind";
|
|
135
|
-
default: return `urgency-${urgency}`;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file How urgent a stated need is, and what follows from that.
|
|
3
|
+
*
|
|
4
|
+
* Five levels, ordered. The ordering is not a set of numbers handed to
|
|
5
|
+
* WebTorrent — measured 2026-09-02, the library does not keep them. Its picker
|
|
6
|
+
* sorts selections by priority only at the moment one is inserted, and then, on
|
|
7
|
+
* every wire it fills, moves the selection it just served to the BACK of the
|
|
8
|
+
* whole group with a non-zero priority:
|
|
9
|
+
*
|
|
10
|
+
* ```js
|
|
11
|
+
* function shufflePriority (i) {
|
|
12
|
+
* let last = i
|
|
13
|
+
* for (let j = i; j < self._selections.length && self._selections.get(j).priority; j++) last = j
|
|
14
|
+
* self._selections.swap(i, last)
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* So distinct non-zero numbers give an order once and a round robin thereafter.
|
|
19
|
+
* Only two things hold: non-zero rotates fairly, zero is always last.
|
|
20
|
+
*
|
|
21
|
+
* Therefore urgency here is expressed by WHETHER a need is stated to the swarm
|
|
22
|
+
* at all. A level is stated only while every level above it is satisfied, and
|
|
23
|
+
* withdrawn the moment one is not — a withdrawn need is not in the download set
|
|
24
|
+
* at all, so a peer with nothing urgent to give cannot fall through to it and
|
|
25
|
+
* spend the shared link on it. A permanently low priority does exactly that,
|
|
26
|
+
* which is why it is not what this does.
|
|
27
|
+
*
|
|
28
|
+
* The rotation is not merely tolerated, it is wanted: with two viewers of one
|
|
29
|
+
* film both stopped, both their needs sit at {@link Urgency.BLOCKED} and the
|
|
30
|
+
* swarm alternates between them instead of always serving whoever asked first.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The levels, most urgent first. The value is the position, so `<` compares
|
|
35
|
+
* urgency and nothing else may be read into it.
|
|
36
|
+
*
|
|
37
|
+
* @enum {number}
|
|
38
|
+
*/
|
|
39
|
+
export const Urgency = {
|
|
40
|
+
/**
|
|
41
|
+
* The bytes a reader is stopped on. The picture is not moving until they
|
|
42
|
+
* arrive.
|
|
43
|
+
*/
|
|
44
|
+
BLOCKED: 0,
|
|
45
|
+
/**
|
|
46
|
+
* The rest of that reader's own window — the cushion being built. Late here
|
|
47
|
+
* and the picture stops in a few seconds.
|
|
48
|
+
*/
|
|
49
|
+
NEAR: 1,
|
|
50
|
+
/**
|
|
51
|
+
* Further along the file, as far as the encoder will reach. Late here costs
|
|
52
|
+
* nothing yet.
|
|
53
|
+
*/
|
|
54
|
+
AHEAD: 2,
|
|
55
|
+
/**
|
|
56
|
+
* To the end of the file. Wanted for certain if the viewer watches on, and
|
|
57
|
+
* not before anything above is satisfied.
|
|
58
|
+
*/
|
|
59
|
+
TAIL: 3,
|
|
60
|
+
/**
|
|
61
|
+
* The gap behind the playhead, left by a forward seek. Wanted only if the
|
|
62
|
+
* viewer seeks back, so it is last — but it is real: a backward seek into a
|
|
63
|
+
* hole is the longest wait this proxy produces (field: 93 s).
|
|
64
|
+
*/
|
|
65
|
+
BEHIND: 4
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Every level, most urgent first. */
|
|
69
|
+
export const URGENCY_ORDER = [
|
|
70
|
+
Urgency.BLOCKED,
|
|
71
|
+
Urgency.NEAR,
|
|
72
|
+
Urgency.AHEAD,
|
|
73
|
+
Urgency.TAIL,
|
|
74
|
+
Urgency.BEHIND
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether a level may take a block away from a slow peer and give it to a fast
|
|
79
|
+
* one — WebTorrent's `critical`.
|
|
80
|
+
*
|
|
81
|
+
* Only the level that is being waited on. Displacement throws away the part of
|
|
82
|
+
* the block the slow peer had already fetched, so it buys time exactly where
|
|
83
|
+
* time is what is short, and wastes bandwidth everywhere else.
|
|
84
|
+
*
|
|
85
|
+
* @param {number} urgency
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
export function mayDisplaceSlowPeer(urgency) {
|
|
89
|
+
return urgency === Urgency.BLOCKED;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Whether a level is stated to the swarm only while the levels above it want
|
|
94
|
+
* nothing.
|
|
95
|
+
*
|
|
96
|
+
* The three urgent levels are always stated: they are what the viewer is
|
|
97
|
+
* waiting for. The two speculative ones are stated only in the room the urgent
|
|
98
|
+
* ones leave.
|
|
99
|
+
*
|
|
100
|
+
* @param {number} urgency
|
|
101
|
+
* @returns {boolean}
|
|
102
|
+
*/
|
|
103
|
+
export function isConditional(urgency) {
|
|
104
|
+
return urgency >= Urgency.TAIL;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The number handed to `torrent.select`.
|
|
109
|
+
*
|
|
110
|
+
* Two values only, because two is all the library keeps: everything urgent
|
|
111
|
+
* shares one non-zero priority and rotates fairly among itself; the
|
|
112
|
+
* speculative levels take zero, which the library always places last and never
|
|
113
|
+
* rotates into the group above.
|
|
114
|
+
*
|
|
115
|
+
* @param {number} urgency
|
|
116
|
+
* @returns {number}
|
|
117
|
+
*/
|
|
118
|
+
export function selectionPriority(urgency) {
|
|
119
|
+
return isConditional(urgency) ? 0 : 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A level's name, for the log.
|
|
124
|
+
*
|
|
125
|
+
* @param {number} urgency
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
export function urgencyName(urgency) {
|
|
129
|
+
switch (urgency) {
|
|
130
|
+
case Urgency.BLOCKED: return "blocked";
|
|
131
|
+
case Urgency.NEAR: return "near";
|
|
132
|
+
case Urgency.AHEAD: return "ahead";
|
|
133
|
+
case Urgency.TAIL: return "tail";
|
|
134
|
+
case Urgency.BEHIND: return "behind";
|
|
135
|
+
default: return `urgency-${urgency}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One stated need: bytes of one file, by one claimant, at one urgency.
|
|
3
|
-
*
|
|
4
|
-
* **Bytes, not pieces.** A piece is how the protocol verifies data and how
|
|
5
|
-
* memory is allocated; it is not the unit anything else should count in.
|
|
6
|
-
* Counting in pieces means dividing and rounding down at every boundary, and
|
|
7
|
-
* that rounding is where a real failure lives: with 16 MiB pieces a 64 MB
|
|
8
|
-
* allowance is four places, while two readers asking for 96 MB each want six —
|
|
9
|
-
* every resident piece then ends up pinned, the read returns zero bytes, and
|
|
10
|
-
* ffmpeg takes that for the end of the file. In bytes the shortage is plain;
|
|
11
|
-
* in pieces it is hidden behind a floor. The single conversion lives in
|
|
12
|
-
* `pieces.js`.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A range of one file that somebody says they will need.
|
|
17
|
-
*/
|
|
18
|
-
export class Window {
|
|
19
|
-
/**
|
|
20
|
-
* @param {object} params
|
|
21
|
-
* @param {string} params.claimant - Who states it. A release names this, so a
|
|
22
|
-
* stray release matches nothing rather than cancelling somebody else's
|
|
23
|
-
* need.
|
|
24
|
-
* @param {number} params.fileIndex - Which file of the torrent.
|
|
25
|
-
* @param {number} params.byteStart - First byte, inclusive.
|
|
26
|
-
* @param {number} params.byteEnd - Last byte, inclusive.
|
|
27
|
-
* @param {number} params.urgency - A value of {@link import("./Urgency.js").Urgency}.
|
|
28
|
-
*/
|
|
29
|
-
constructor({ claimant, fileIndex, byteStart, byteEnd, urgency }) {
|
|
30
|
-
if (typeof claimant !== "string" || claimant.length === 0) {
|
|
31
|
-
throw new Error("A window needs a claimant to release it by.");
|
|
32
|
-
}
|
|
33
|
-
if (!Number.isInteger(fileIndex) || fileIndex < 0) {
|
|
34
|
-
throw new Error(`File index must be a non-negative integer, got ${fileIndex}.`);
|
|
35
|
-
}
|
|
36
|
-
if (!Number.isInteger(byteStart) || byteStart < 0) {
|
|
37
|
-
throw new Error(`Byte start must be a non-negative integer, got ${byteStart}.`);
|
|
38
|
-
}
|
|
39
|
-
if (!Number.isInteger(byteEnd) || byteEnd < byteStart) {
|
|
40
|
-
throw new Error(`Byte end must be an integer at or after ${byteStart}, got ${byteEnd}.`);
|
|
41
|
-
}
|
|
42
|
-
if (!Number.isInteger(urgency) || urgency < 0) {
|
|
43
|
-
throw new Error(`Urgency must be a non-negative integer, got ${urgency}.`);
|
|
44
|
-
}
|
|
45
|
-
this.claimant = claimant;
|
|
46
|
-
this.fileIndex = fileIndex;
|
|
47
|
-
this.byteStart = byteStart;
|
|
48
|
-
this.byteEnd = byteEnd;
|
|
49
|
-
this.urgency = urgency;
|
|
50
|
-
Object.freeze(this);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** How many bytes this window covers. */
|
|
54
|
-
get byteLength() {
|
|
55
|
-
return this.byteEnd - this.byteStart + 1;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Whether two windows cover any of the same bytes of the same file.
|
|
60
|
-
*
|
|
61
|
-
* @param {Window} other
|
|
62
|
-
* @returns {boolean}
|
|
63
|
-
*/
|
|
64
|
-
overlaps(other) {
|
|
65
|
-
return this.fileIndex === other.fileIndex
|
|
66
|
-
&& this.byteStart <= other.byteEnd
|
|
67
|
-
&& other.byteStart <= this.byteEnd;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Whether two windows are the same statement — same file, same bytes, same
|
|
72
|
-
* urgency, same claimant.
|
|
73
|
-
*
|
|
74
|
-
* @param {Window} other
|
|
75
|
-
* @returns {boolean}
|
|
76
|
-
*/
|
|
77
|
-
equals(other) {
|
|
78
|
-
return other instanceof Window
|
|
79
|
-
&& this.claimant === other.claimant
|
|
80
|
-
&& this.fileIndex === other.fileIndex
|
|
81
|
-
&& this.byteStart === other.byteStart
|
|
82
|
-
&& this.byteEnd === other.byteEnd
|
|
83
|
-
&& this.urgency === other.urgency;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** @returns {string} */
|
|
87
|
-
toString() {
|
|
88
|
-
return `${this.claimant} wants ${this.fileIndex}:${this.byteStart}-${this.byteEnd}`;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The union of a set of ranges of ONE file, in order, with touching and
|
|
94
|
-
* overlapping ones merged.
|
|
95
|
-
*
|
|
96
|
-
* The union and not the sum: a viewer's picture and sound are read by two
|
|
97
|
-
* readers of the same file whose windows overlap by construction, and adding
|
|
98
|
-
* them would report a demand that is not there.
|
|
99
|
-
*
|
|
100
|
-
* @param {Array<{ byteStart: number, byteEnd: number }>} ranges
|
|
101
|
-
* @returns {Array<{ byteStart: number, byteEnd: number }>}
|
|
102
|
-
*/
|
|
103
|
-
export function unionOf(ranges) {
|
|
104
|
-
const sorted = [...ranges].sort((left, right) => left.byteStart - right.byteStart);
|
|
105
|
-
/** @type {Array<{ byteStart: number, byteEnd: number }>} */
|
|
106
|
-
const merged = [];
|
|
107
|
-
for (const range of sorted) {
|
|
108
|
-
const last = merged[merged.length - 1];
|
|
109
|
-
// `+ 1` because ranges are inclusive: 0-99 and 100-199 are one run of 200
|
|
110
|
-
// bytes, not two runs with nothing between them.
|
|
111
|
-
if (last && range.byteStart <= last.byteEnd + 1) {
|
|
112
|
-
last.byteEnd = Math.max(last.byteEnd, range.byteEnd);
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
merged.push({ byteStart: range.byteStart, byteEnd: range.byteEnd });
|
|
116
|
-
}
|
|
117
|
-
return merged;
|
|
118
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file One stated need: bytes of one file, by one claimant, at one urgency.
|
|
3
|
+
*
|
|
4
|
+
* **Bytes, not pieces.** A piece is how the protocol verifies data and how
|
|
5
|
+
* memory is allocated; it is not the unit anything else should count in.
|
|
6
|
+
* Counting in pieces means dividing and rounding down at every boundary, and
|
|
7
|
+
* that rounding is where a real failure lives: with 16 MiB pieces a 64 MB
|
|
8
|
+
* allowance is four places, while two readers asking for 96 MB each want six —
|
|
9
|
+
* every resident piece then ends up pinned, the read returns zero bytes, and
|
|
10
|
+
* ffmpeg takes that for the end of the file. In bytes the shortage is plain;
|
|
11
|
+
* in pieces it is hidden behind a floor. The single conversion lives in
|
|
12
|
+
* `pieces.js`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A range of one file that somebody says they will need.
|
|
17
|
+
*/
|
|
18
|
+
export class Window {
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} params
|
|
21
|
+
* @param {string} params.claimant - Who states it. A release names this, so a
|
|
22
|
+
* stray release matches nothing rather than cancelling somebody else's
|
|
23
|
+
* need.
|
|
24
|
+
* @param {number} params.fileIndex - Which file of the torrent.
|
|
25
|
+
* @param {number} params.byteStart - First byte, inclusive.
|
|
26
|
+
* @param {number} params.byteEnd - Last byte, inclusive.
|
|
27
|
+
* @param {number} params.urgency - A value of {@link import("./Urgency.js").Urgency}.
|
|
28
|
+
*/
|
|
29
|
+
constructor({ claimant, fileIndex, byteStart, byteEnd, urgency }) {
|
|
30
|
+
if (typeof claimant !== "string" || claimant.length === 0) {
|
|
31
|
+
throw new Error("A window needs a claimant to release it by.");
|
|
32
|
+
}
|
|
33
|
+
if (!Number.isInteger(fileIndex) || fileIndex < 0) {
|
|
34
|
+
throw new Error(`File index must be a non-negative integer, got ${fileIndex}.`);
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isInteger(byteStart) || byteStart < 0) {
|
|
37
|
+
throw new Error(`Byte start must be a non-negative integer, got ${byteStart}.`);
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isInteger(byteEnd) || byteEnd < byteStart) {
|
|
40
|
+
throw new Error(`Byte end must be an integer at or after ${byteStart}, got ${byteEnd}.`);
|
|
41
|
+
}
|
|
42
|
+
if (!Number.isInteger(urgency) || urgency < 0) {
|
|
43
|
+
throw new Error(`Urgency must be a non-negative integer, got ${urgency}.`);
|
|
44
|
+
}
|
|
45
|
+
this.claimant = claimant;
|
|
46
|
+
this.fileIndex = fileIndex;
|
|
47
|
+
this.byteStart = byteStart;
|
|
48
|
+
this.byteEnd = byteEnd;
|
|
49
|
+
this.urgency = urgency;
|
|
50
|
+
Object.freeze(this);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** How many bytes this window covers. */
|
|
54
|
+
get byteLength() {
|
|
55
|
+
return this.byteEnd - this.byteStart + 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether two windows cover any of the same bytes of the same file.
|
|
60
|
+
*
|
|
61
|
+
* @param {Window} other
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
overlaps(other) {
|
|
65
|
+
return this.fileIndex === other.fileIndex
|
|
66
|
+
&& this.byteStart <= other.byteEnd
|
|
67
|
+
&& other.byteStart <= this.byteEnd;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether two windows are the same statement — same file, same bytes, same
|
|
72
|
+
* urgency, same claimant.
|
|
73
|
+
*
|
|
74
|
+
* @param {Window} other
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
equals(other) {
|
|
78
|
+
return other instanceof Window
|
|
79
|
+
&& this.claimant === other.claimant
|
|
80
|
+
&& this.fileIndex === other.fileIndex
|
|
81
|
+
&& this.byteStart === other.byteStart
|
|
82
|
+
&& this.byteEnd === other.byteEnd
|
|
83
|
+
&& this.urgency === other.urgency;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @returns {string} */
|
|
87
|
+
toString() {
|
|
88
|
+
return `${this.claimant} wants ${this.fileIndex}:${this.byteStart}-${this.byteEnd}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The union of a set of ranges of ONE file, in order, with touching and
|
|
94
|
+
* overlapping ones merged.
|
|
95
|
+
*
|
|
96
|
+
* The union and not the sum: a viewer's picture and sound are read by two
|
|
97
|
+
* readers of the same file whose windows overlap by construction, and adding
|
|
98
|
+
* them would report a demand that is not there.
|
|
99
|
+
*
|
|
100
|
+
* @param {Array<{ byteStart: number, byteEnd: number }>} ranges
|
|
101
|
+
* @returns {Array<{ byteStart: number, byteEnd: number }>}
|
|
102
|
+
*/
|
|
103
|
+
export function unionOf(ranges) {
|
|
104
|
+
const sorted = [...ranges].sort((left, right) => left.byteStart - right.byteStart);
|
|
105
|
+
/** @type {Array<{ byteStart: number, byteEnd: number }>} */
|
|
106
|
+
const merged = [];
|
|
107
|
+
for (const range of sorted) {
|
|
108
|
+
const last = merged[merged.length - 1];
|
|
109
|
+
// `+ 1` because ranges are inclusive: 0-99 and 100-199 are one run of 200
|
|
110
|
+
// bytes, not two runs with nothing between them.
|
|
111
|
+
if (last && range.byteStart <= last.byteEnd + 1) {
|
|
112
|
+
last.byteEnd = Math.max(last.byteEnd, range.byteEnd);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
merged.push({ byteStart: range.byteStart, byteEnd: range.byteEnd });
|
|
116
|
+
}
|
|
117
|
+
return merged;
|
|
118
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Only what is imported through this file. `unionOf`, `mayDisplaceSlowPeer`,
|
|
2
|
-
// `URGENCY_ORDER`, `nearestFirst`, `piecesNeededFor` and `piecesWithin` were
|
|
3
|
-
// re-exported here and taken by nobody; each is still where it is declared, and
|
|
4
|
-
// still used there.
|
|
5
|
-
export { DemandRegister } from "./DemandRegister.js";
|
|
6
|
-
export { Window } from "./Window.js";
|
|
7
|
-
export { isConditional, selectionPriority, Urgency, urgencyName } from "./Urgency.js";
|
|
8
|
-
export { bytesOf, piecesOf } from "./pieces.js";
|
|
1
|
+
// Only what is imported through this file. `unionOf`, `mayDisplaceSlowPeer`,
|
|
2
|
+
// `URGENCY_ORDER`, `nearestFirst`, `piecesNeededFor` and `piecesWithin` were
|
|
3
|
+
// re-exported here and taken by nobody; each is still where it is declared, and
|
|
4
|
+
// still used there.
|
|
5
|
+
export { DemandRegister } from "./DemandRegister.js";
|
|
6
|
+
export { Window } from "./Window.js";
|
|
7
|
+
export { isConditional, selectionPriority, Urgency, urgencyName } from "./Urgency.js";
|
|
8
|
+
export { bytesOf, piecesOf } from "./pieces.js";
|