@torrent-tv/proxy 2.89.6 → 2.89.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +338 -338
- package/CHANGELOG.md +5 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +765 -765
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +17 -2
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +273 -273
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +416 -416
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -95
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/server.js +748 -748
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1192 -1192
- package/services/encode/EncodePlan.js +1368 -1368
- package/services/encode/EncodeRun.js +32 -4
- package/services/encode/EncodeRuns.js +19 -7
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2283 -2283
- package/services/encode/RunClock.js +87 -87
- package/services/encode/RunProgress.js +79 -79
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +418 -418
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +116 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +43 -43
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/piece-completeness.js +60 -2
- package/services/encode/quality/EncodeCost.js +1043 -1043
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +24 -24
- package/services/encode/quality/QualityController.js +758 -758
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +337 -337
- package/services/encode/quality/output-format.js +319 -319
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +3 -1
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +53 -53
- package/services/encode/segment-formats/fmp4.js +476 -476
- package/services/encode/segment-formats/index.js +1 -0
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +8 -1
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1152 -1152
- package/services/server/ViewerRequests.js +785 -785
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +1029 -1029
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/returns.js +103 -103
- package/services/storage/wire.js +175 -175
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +264 -264
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/media-time.js +355 -355
- package/services/viewer/playback-readiness.js +635 -635
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-buffer-continuity.test.js +77 -77
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +275 -275
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +426 -426
- package/test/encode-plan-viewers.test.js +725 -725
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +38 -0
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +68 -68
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fixtures/refused-audio-20261002.json +10 -0
- package/test/fmp4-presentation-clock.test.js +4 -2
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +240 -240
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +291 -291
- package/test/matroska-cues-track.test.js +192 -192
- package/test/media-time.test.js +62 -62
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +207 -207
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +87 -87
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-completeness.test.js +37 -1
- package/test/piece-disk-store.test.js +401 -401
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +518 -518
- package/test/playback-start-regressions.test.js +101 -101
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +160 -160
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +64 -64
- package/test/same-height-switch.test.js +663 -663
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +352 -352
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +237 -237
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/supply-margin.test.js +190 -190
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { bodySender } from "../services/transport/data-channel-handler.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Drives the sender over a body arriving in the given read sizes, and answers
|
|
7
|
-
* both what reached the far end and what was written — so "the body is
|
|
8
|
-
* unchanged" is asserted against the body rather than against a length.
|
|
9
|
-
*
|
|
10
|
-
* @param {number} sizeBytes
|
|
11
|
-
* @param {number[]} readSizes - The sizes the body read hands over, in order.
|
|
12
|
-
* @returns {{ sent: Buffer[], counted: number, written: Buffer }}
|
|
13
|
-
*/
|
|
14
|
-
function sendBody(sizeBytes, readSizes) {
|
|
15
|
-
/** @type {Buffer[]} */
|
|
16
|
-
const sent = [];
|
|
17
|
-
const sender = bodySender((bytes) => sent.push(Buffer.from(bytes)), sizeBytes);
|
|
18
|
-
/** @type {Buffer[]} */
|
|
19
|
-
const written = [];
|
|
20
|
-
let counted = 0;
|
|
21
|
-
let next = 0;
|
|
22
|
-
for (const size of readSizes) {
|
|
23
|
-
const block = Buffer.alloc(size);
|
|
24
|
-
for (let index = 0; index < size; index += 1) {
|
|
25
|
-
block[index] = next & 0xff;
|
|
26
|
-
next += 1;
|
|
27
|
-
}
|
|
28
|
-
written.push(block);
|
|
29
|
-
counted += sender.push(block);
|
|
30
|
-
}
|
|
31
|
-
counted += sender.flush();
|
|
32
|
-
return { sent, counted, written: Buffer.concat(written) };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
test("with no size chosen, one body read is one message", () => {
|
|
36
|
-
const { sent, counted } = sendBody(0, [1000, 2000, 3]);
|
|
37
|
-
assert.deepEqual(
|
|
38
|
-
sent.map((message) => message.length),
|
|
39
|
-
[1000, 2000, 3]
|
|
40
|
-
);
|
|
41
|
-
assert.equal(counted, 3);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("a smaller size cuts a body read into messages of exactly that size", () => {
|
|
45
|
-
const { sent, counted } = sendBody(400, [1000]);
|
|
46
|
-
assert.deepEqual(
|
|
47
|
-
sent.map((message) => message.length),
|
|
48
|
-
[400, 400, 200]
|
|
49
|
-
);
|
|
50
|
-
assert.equal(counted, 3);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("a larger size joins body reads until it is filled", () => {
|
|
54
|
-
const { sent } = sendBody(2500, [1000, 1000, 1000, 1000]);
|
|
55
|
-
assert.deepEqual(
|
|
56
|
-
sent.map((message) => message.length),
|
|
57
|
-
[2500, 1500]
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("every byte arrives once, in order, whatever the size", () => {
|
|
62
|
-
for (const size of [0, 1, 7, 400, 2500, 999_999]) {
|
|
63
|
-
const { sent, written } = sendBody(size, [1000, 3, 5000, 17]);
|
|
64
|
-
assert.deepEqual(Buffer.concat(sent), written, `size ${size} changed the body`);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("a size larger than the whole body still sends it, once, on flush", () => {
|
|
69
|
-
const { sent, written, counted } = sendBody(10_000, [100, 200]);
|
|
70
|
-
assert.equal(sent.length, 1);
|
|
71
|
-
assert.equal(counted, 1);
|
|
72
|
-
assert.deepEqual(sent[0], written);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("the count is of messages, which is what a reading is attributed to", () => {
|
|
76
|
-
// 163 messages for 10 657 210 bytes is the field reading of 2026-09-12; at
|
|
77
|
-
// 16 KB the same body is 651 of them, and that difference is the measurement.
|
|
78
|
-
const { counted } = sendBody(16 * 1024, [10_657_210]);
|
|
79
|
-
assert.equal(counted, Math.ceil(10_657_210 / (16 * 1024)));
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("nothing is sent for an empty body, and flush has nothing to release", () => {
|
|
83
|
-
const { sent, counted } = sendBody(400, []);
|
|
84
|
-
assert.deepEqual(sent, []);
|
|
85
|
-
assert.equal(counted, 0);
|
|
86
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { bodySender } from "../services/transport/data-channel-handler.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Drives the sender over a body arriving in the given read sizes, and answers
|
|
7
|
+
* both what reached the far end and what was written — so "the body is
|
|
8
|
+
* unchanged" is asserted against the body rather than against a length.
|
|
9
|
+
*
|
|
10
|
+
* @param {number} sizeBytes
|
|
11
|
+
* @param {number[]} readSizes - The sizes the body read hands over, in order.
|
|
12
|
+
* @returns {{ sent: Buffer[], counted: number, written: Buffer }}
|
|
13
|
+
*/
|
|
14
|
+
function sendBody(sizeBytes, readSizes) {
|
|
15
|
+
/** @type {Buffer[]} */
|
|
16
|
+
const sent = [];
|
|
17
|
+
const sender = bodySender((bytes) => sent.push(Buffer.from(bytes)), sizeBytes);
|
|
18
|
+
/** @type {Buffer[]} */
|
|
19
|
+
const written = [];
|
|
20
|
+
let counted = 0;
|
|
21
|
+
let next = 0;
|
|
22
|
+
for (const size of readSizes) {
|
|
23
|
+
const block = Buffer.alloc(size);
|
|
24
|
+
for (let index = 0; index < size; index += 1) {
|
|
25
|
+
block[index] = next & 0xff;
|
|
26
|
+
next += 1;
|
|
27
|
+
}
|
|
28
|
+
written.push(block);
|
|
29
|
+
counted += sender.push(block);
|
|
30
|
+
}
|
|
31
|
+
counted += sender.flush();
|
|
32
|
+
return { sent, counted, written: Buffer.concat(written) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("with no size chosen, one body read is one message", () => {
|
|
36
|
+
const { sent, counted } = sendBody(0, [1000, 2000, 3]);
|
|
37
|
+
assert.deepEqual(
|
|
38
|
+
sent.map((message) => message.length),
|
|
39
|
+
[1000, 2000, 3]
|
|
40
|
+
);
|
|
41
|
+
assert.equal(counted, 3);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("a smaller size cuts a body read into messages of exactly that size", () => {
|
|
45
|
+
const { sent, counted } = sendBody(400, [1000]);
|
|
46
|
+
assert.deepEqual(
|
|
47
|
+
sent.map((message) => message.length),
|
|
48
|
+
[400, 400, 200]
|
|
49
|
+
);
|
|
50
|
+
assert.equal(counted, 3);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("a larger size joins body reads until it is filled", () => {
|
|
54
|
+
const { sent } = sendBody(2500, [1000, 1000, 1000, 1000]);
|
|
55
|
+
assert.deepEqual(
|
|
56
|
+
sent.map((message) => message.length),
|
|
57
|
+
[2500, 1500]
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("every byte arrives once, in order, whatever the size", () => {
|
|
62
|
+
for (const size of [0, 1, 7, 400, 2500, 999_999]) {
|
|
63
|
+
const { sent, written } = sendBody(size, [1000, 3, 5000, 17]);
|
|
64
|
+
assert.deepEqual(Buffer.concat(sent), written, `size ${size} changed the body`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("a size larger than the whole body still sends it, once, on flush", () => {
|
|
69
|
+
const { sent, written, counted } = sendBody(10_000, [100, 200]);
|
|
70
|
+
assert.equal(sent.length, 1);
|
|
71
|
+
assert.equal(counted, 1);
|
|
72
|
+
assert.deepEqual(sent[0], written);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("the count is of messages, which is what a reading is attributed to", () => {
|
|
76
|
+
// 163 messages for 10 657 210 bytes is the field reading of 2026-09-12; at
|
|
77
|
+
// 16 KB the same body is 651 of them, and that difference is the measurement.
|
|
78
|
+
const { counted } = sendBody(16 * 1024, [10_657_210]);
|
|
79
|
+
assert.equal(counted, Math.ceil(10_657_210 / (16 * 1024)));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("nothing is sent for an empty body, and flush has nothing to release", () => {
|
|
83
|
+
const { sent, counted } = sendBody(400, []);
|
|
84
|
+
assert.deepEqual(sent, []);
|
|
85
|
+
assert.equal(counted, 0);
|
|
86
|
+
});
|
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Which existing output serves a viewer: the rules decided with the user
|
|
3
|
-
* 2026-09-16, with the viewer's own link asked in every mode and on every
|
|
4
|
-
* branch (roadmap item 97, step 11).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import test from "node:test";
|
|
8
|
-
import assert from "node:assert/strict";
|
|
9
|
-
import { chooseServingOutput } from "../services/encode/quality/serving-output.js";
|
|
10
|
-
import { linkCouldCarry, loadOf, PEAK_CLASS } from "../services/encode/quality/link-budget.js";
|
|
11
|
-
|
|
12
|
-
const WANTED = { width: 1824, height: 1026 };
|
|
13
|
-
const NEXT_LOWER = 1280 * 720;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @param {string} key
|
|
17
|
-
* @param {number} width
|
|
18
|
-
* @param {number} height
|
|
19
|
-
* @param {{ mbps?: number | null, peakClass?: string, readyHere?: boolean }} [over]
|
|
20
|
-
*/
|
|
21
|
-
function candidate(key, width, height, { mbps = 5, peakClass = PEAK_CLASS.KNOWN, readyHere = true } = {}) {
|
|
22
|
-
return { key, width, height, readyHere, load: loadOf({ mbps, peakClass: mbps === null ? PEAK_CLASS.UNKNOWN : peakClass }, null) };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {object} over
|
|
27
|
-
* @param {number | null} [linkMbps] - What this viewer's link measured.
|
|
28
|
-
*/
|
|
29
|
-
function choose(over, linkMbps = null) {
|
|
30
|
-
return chooseServingOutput({
|
|
31
|
-
mode: "auto",
|
|
32
|
-
wanted: WANTED,
|
|
33
|
-
nextLowerArea: NEXT_LOWER,
|
|
34
|
-
atRisk: false,
|
|
35
|
-
judge: (one) => linkCouldCarry(linkMbps, one.load),
|
|
36
|
-
candidates: [],
|
|
37
|
-
...over
|
|
38
|
-
})?.key ?? null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
test("the same quality or higher is served, the nearest of them", () => {
|
|
42
|
-
assert.equal(choose({ candidates: [candidate("2160", 3840, 2160), candidate("1080", 1920, 1080)] }), "1080");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("higher quality is not served over a link it does not fit", () => {
|
|
46
|
-
assert.equal(choose({ candidates: [candidate("1080", 1920, 1080, { mbps: 12 })] }, 10), null);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("a little lower — above the next rung down — is served, and only if the link admits it", () => {
|
|
50
|
-
assert.equal(choose({ candidates: [candidate("1000", 1776, 1000)] }), "1000");
|
|
51
|
-
assert.equal(
|
|
52
|
-
choose({ candidates: [candidate("1000", 1776, 1000, { mbps: 12 })] }, 10),
|
|
53
|
-
null,
|
|
54
|
-
"a smaller picture proves nothing by itself: its own load is asked"
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("the next rung down or lower is served only when making the wanted format risks a wait, and only if it fits", () => {
|
|
59
|
-
const lower = [candidate("720", 1280, 720), candidate("480", 854, 480)];
|
|
60
|
-
assert.equal(choose({ candidates: lower }), null);
|
|
61
|
-
assert.equal(choose({ candidates: lower, atRisk: true }), "720");
|
|
62
|
-
assert.equal(
|
|
63
|
-
choose({ candidates: [candidate("720", 1280, 720, { mbps: 12 }), candidate("480", 854, 480, { mbps: 3 })], atRisk: true }, 10),
|
|
64
|
-
"480",
|
|
65
|
-
"the larger one does not fit this link, so it is not taken"
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("an output without the piece at the viewer's position spares them nothing", () => {
|
|
70
|
-
assert.equal(choose({ candidates: [candidate("1080", 1920, 1080, { readyHere: false })] }), null);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("a size chosen by hand is served only exactly, and only if the link admits it", () => {
|
|
74
|
-
const candidates = [candidate("1080", 1920, 1080), candidate("exact", 1824, 1026)];
|
|
75
|
-
assert.equal(choose({ mode: "manual", candidates }), "exact");
|
|
76
|
-
assert.equal(choose({ mode: "manual", candidates: [candidate("1080", 1920, 1080)], atRisk: true }), null);
|
|
77
|
-
assert.equal(
|
|
78
|
-
choose({ mode: "manual", candidates: [candidate("exact", 1824, 1026, { mbps: 12 })] }, 10),
|
|
79
|
-
null,
|
|
80
|
-
"the size they picked, but more than their link carries: not handed over"
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("of two outputs of the size picked by hand, the higher limit the link admits is served", () => {
|
|
85
|
-
const candidates = [candidate("2800", 1824, 1026, { mbps: 3.64 }), candidate("1400", 1824, 1026, { mbps: 1.82 })];
|
|
86
|
-
assert.equal(choose({ mode: "manual", candidates }, 10), "2800");
|
|
87
|
-
assert.equal(choose({ mode: "manual", candidates }, 3), "1400", "the link admits only the lower limit");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("a bound is taken before an average, even when the average's limit is higher", () => {
|
|
91
|
-
const candidates = [
|
|
92
|
-
candidate("estimated", 1824, 1026, { mbps: 6, peakClass: PEAK_CLASS.ESTIMATED }),
|
|
93
|
-
candidate("known", 1824, 1026, { mbps: 4, peakClass: PEAK_CLASS.KNOWN })
|
|
94
|
-
];
|
|
95
|
-
assert.equal(choose({ mode: "manual", candidates }, 10), "known");
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("an output with no bound is not served against a measured link, and is while nothing measured it", () => {
|
|
99
|
-
const candidates = [candidate("hardware", 1824, 1026, { mbps: null })];
|
|
100
|
-
assert.equal(choose({ mode: "manual", candidates }, 50), null);
|
|
101
|
-
assert.equal(choose({ mode: "manual", candidates }, null), "hardware");
|
|
102
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file Which existing output serves a viewer: the rules decided with the user
|
|
3
|
+
* 2026-09-16, with the viewer's own link asked in every mode and on every
|
|
4
|
+
* branch (roadmap item 97, step 11).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { chooseServingOutput } from "../services/encode/quality/serving-output.js";
|
|
10
|
+
import { linkCouldCarry, loadOf, PEAK_CLASS } from "../services/encode/quality/link-budget.js";
|
|
11
|
+
|
|
12
|
+
const WANTED = { width: 1824, height: 1026 };
|
|
13
|
+
const NEXT_LOWER = 1280 * 720;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} key
|
|
17
|
+
* @param {number} width
|
|
18
|
+
* @param {number} height
|
|
19
|
+
* @param {{ mbps?: number | null, peakClass?: string, readyHere?: boolean }} [over]
|
|
20
|
+
*/
|
|
21
|
+
function candidate(key, width, height, { mbps = 5, peakClass = PEAK_CLASS.KNOWN, readyHere = true } = {}) {
|
|
22
|
+
return { key, width, height, readyHere, load: loadOf({ mbps, peakClass: mbps === null ? PEAK_CLASS.UNKNOWN : peakClass }, null) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {object} over
|
|
27
|
+
* @param {number | null} [linkMbps] - What this viewer's link measured.
|
|
28
|
+
*/
|
|
29
|
+
function choose(over, linkMbps = null) {
|
|
30
|
+
return chooseServingOutput({
|
|
31
|
+
mode: "auto",
|
|
32
|
+
wanted: WANTED,
|
|
33
|
+
nextLowerArea: NEXT_LOWER,
|
|
34
|
+
atRisk: false,
|
|
35
|
+
judge: (one) => linkCouldCarry(linkMbps, one.load),
|
|
36
|
+
candidates: [],
|
|
37
|
+
...over
|
|
38
|
+
})?.key ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("the same quality or higher is served, the nearest of them", () => {
|
|
42
|
+
assert.equal(choose({ candidates: [candidate("2160", 3840, 2160), candidate("1080", 1920, 1080)] }), "1080");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("higher quality is not served over a link it does not fit", () => {
|
|
46
|
+
assert.equal(choose({ candidates: [candidate("1080", 1920, 1080, { mbps: 12 })] }, 10), null);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a little lower — above the next rung down — is served, and only if the link admits it", () => {
|
|
50
|
+
assert.equal(choose({ candidates: [candidate("1000", 1776, 1000)] }), "1000");
|
|
51
|
+
assert.equal(
|
|
52
|
+
choose({ candidates: [candidate("1000", 1776, 1000, { mbps: 12 })] }, 10),
|
|
53
|
+
null,
|
|
54
|
+
"a smaller picture proves nothing by itself: its own load is asked"
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("the next rung down or lower is served only when making the wanted format risks a wait, and only if it fits", () => {
|
|
59
|
+
const lower = [candidate("720", 1280, 720), candidate("480", 854, 480)];
|
|
60
|
+
assert.equal(choose({ candidates: lower }), null);
|
|
61
|
+
assert.equal(choose({ candidates: lower, atRisk: true }), "720");
|
|
62
|
+
assert.equal(
|
|
63
|
+
choose({ candidates: [candidate("720", 1280, 720, { mbps: 12 }), candidate("480", 854, 480, { mbps: 3 })], atRisk: true }, 10),
|
|
64
|
+
"480",
|
|
65
|
+
"the larger one does not fit this link, so it is not taken"
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("an output without the piece at the viewer's position spares them nothing", () => {
|
|
70
|
+
assert.equal(choose({ candidates: [candidate("1080", 1920, 1080, { readyHere: false })] }), null);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("a size chosen by hand is served only exactly, and only if the link admits it", () => {
|
|
74
|
+
const candidates = [candidate("1080", 1920, 1080), candidate("exact", 1824, 1026)];
|
|
75
|
+
assert.equal(choose({ mode: "manual", candidates }), "exact");
|
|
76
|
+
assert.equal(choose({ mode: "manual", candidates: [candidate("1080", 1920, 1080)], atRisk: true }), null);
|
|
77
|
+
assert.equal(
|
|
78
|
+
choose({ mode: "manual", candidates: [candidate("exact", 1824, 1026, { mbps: 12 })] }, 10),
|
|
79
|
+
null,
|
|
80
|
+
"the size they picked, but more than their link carries: not handed over"
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("of two outputs of the size picked by hand, the higher limit the link admits is served", () => {
|
|
85
|
+
const candidates = [candidate("2800", 1824, 1026, { mbps: 3.64 }), candidate("1400", 1824, 1026, { mbps: 1.82 })];
|
|
86
|
+
assert.equal(choose({ mode: "manual", candidates }, 10), "2800");
|
|
87
|
+
assert.equal(choose({ mode: "manual", candidates }, 3), "1400", "the link admits only the lower limit");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("a bound is taken before an average, even when the average's limit is higher", () => {
|
|
91
|
+
const candidates = [
|
|
92
|
+
candidate("estimated", 1824, 1026, { mbps: 6, peakClass: PEAK_CLASS.ESTIMATED }),
|
|
93
|
+
candidate("known", 1824, 1026, { mbps: 4, peakClass: PEAK_CLASS.KNOWN })
|
|
94
|
+
];
|
|
95
|
+
assert.equal(choose({ mode: "manual", candidates }, 10), "known");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("an output with no bound is not served against a measured link, and is while nothing measured it", () => {
|
|
99
|
+
const candidates = [candidate("hardware", 1824, 1026, { mbps: null })];
|
|
100
|
+
assert.equal(choose({ mode: "manual", candidates }, 50), null);
|
|
101
|
+
assert.equal(choose({ mode: "manual", candidates }, null), "hardware");
|
|
102
|
+
});
|