@torrent-tv/proxy 2.87.21 → 2.88.0
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 +133 -133
- 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 +50 -50
- 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/files/get.js +106 -100
- 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 +173 -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 +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 +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 +758 -758
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2184 -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 +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 +1141 -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 +254 -254
- package/services/encode/quality/output-format.js +315 -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/mp4-boxes.js +515 -515
- 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 -245
- 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 +931 -931
- 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 +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/playback-readiness.js +696 -696
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +992 -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/Contents.js +382 -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/episode-naming.js +182 -0
- 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-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 -0
- 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-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 +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 +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 +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 +12 -2
- 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 +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,180 +1,180 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file The bitrate limit and the level are decided with the rest of an
|
|
3
|
-
* output's format, before it is named — roadmap item 97, step 10.
|
|
4
|
-
*
|
|
5
|
-
* Nothing here starts ffmpeg, a torrent or a worker: the decision is a pure
|
|
6
|
-
* function of values.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import test from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { decideOutputFormat } from "../services/encode/quality/output-format.js";
|
|
12
|
-
import { nominalKbpsFor, softwareRateControlFor } from "../services/encode/args.js";
|
|
13
|
-
import { AudioOutput, CutGrid, OutputSpec, VideoOutput } from "../services/encode/output/OutputSpec.js";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A 720p output asked for exactly, from a 1080p source, on the encoder given.
|
|
17
|
-
*
|
|
18
|
-
* @param {{ kind: string, name: string }} encoder
|
|
19
|
-
* @param {number | null} [capKbps]
|
|
20
|
-
* @returns {ReturnType<typeof decideOutputFormat>}
|
|
21
|
-
*/
|
|
22
|
-
function decide(encoder, capKbps = null) {
|
|
23
|
-
return decideOutputFormat({
|
|
24
|
-
encodesPicture: true,
|
|
25
|
-
exact: true,
|
|
26
|
-
target: { width: 1280, height: 720 },
|
|
27
|
-
source: { width: 1920, height: 1080, megabitsPerSecond: 8, decode: null },
|
|
28
|
-
fps: 24,
|
|
29
|
-
encoder,
|
|
30
|
-
benchmark: null,
|
|
31
|
-
cost: { decodeModel: null, observedDecodeCostSec: null, requiredSpeed: null },
|
|
32
|
-
chooseBudget: () => null,
|
|
33
|
-
tonemap: false,
|
|
34
|
-
capKbps,
|
|
35
|
-
specWith: (encode) => new OutputSpec({
|
|
36
|
-
sourceKey: "torrent:abc",
|
|
37
|
-
segmentFormatId: "fmp4",
|
|
38
|
-
grid: new CutGrid({ kind: "uniform", fileIndex: 0 }),
|
|
39
|
-
video: new VideoOutput({ fileIndex: 0, encode }),
|
|
40
|
-
audio: new AudioOutput({ fileIndex: 0, trackIndex: 0, transcode: true })
|
|
41
|
-
}),
|
|
42
|
-
serving: { mode: "manual", linkMbps: null, keys: [], readyAt: () => false }
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const SOFTWARE = { kind: "software", name: "libx264" };
|
|
47
|
-
const HARDWARE = { kind: "nvenc", name: "h264_nvenc" };
|
|
48
|
-
|
|
49
|
-
test("a software output is bounded at its size's nominal rate unless a lower limit is asked for", () => {
|
|
50
|
-
const plain = decide(SOFTWARE).spec.video.encode.rateControl;
|
|
51
|
-
const lower = decide(SOFTWARE, 1400).spec.video.encode.rateControl;
|
|
52
|
-
|
|
53
|
-
assert.deepEqual(plain, softwareRateControlFor({ width: 1280, height: 720, fps: 24 }));
|
|
54
|
-
assert.deepEqual(lower, softwareRateControlFor({ width: 1280, height: 720, fps: 24, capKbps: 1400 }));
|
|
55
|
-
assert.equal(lower.level, plain.level, "both are declared at the nominal output's level");
|
|
56
|
-
assert.notEqual(decide(SOFTWARE).spec.toKey(), decide(SOFTWARE, 1400).spec.toKey(), "and are two outputs");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("a limit named in the request is not answered by an output of another limit already here", () => {
|
|
60
|
-
// The nominal 720p output exists and is ready, and it suits this viewer —
|
|
61
|
-
// which is exactly what a move to a lower limit is for leaving.
|
|
62
|
-
const nominal = decide(SOFTWARE).spec.toKey();
|
|
63
|
-
const asked = decideOutputFormat({
|
|
64
|
-
encodesPicture: true,
|
|
65
|
-
exact: true,
|
|
66
|
-
target: { width: 1280, height: 720 },
|
|
67
|
-
source: { width: 1920, height: 1080, megabitsPerSecond: 8, decode: null },
|
|
68
|
-
fps: 24,
|
|
69
|
-
encoder: SOFTWARE,
|
|
70
|
-
benchmark: null,
|
|
71
|
-
cost: { decodeModel: null, observedDecodeCostSec: null, requiredSpeed: null },
|
|
72
|
-
chooseBudget: () => null,
|
|
73
|
-
tonemap: false,
|
|
74
|
-
capKbps: 1400,
|
|
75
|
-
specWith: (encode) => new OutputSpec({
|
|
76
|
-
sourceKey: "torrent:abc",
|
|
77
|
-
segmentFormatId: "fmp4",
|
|
78
|
-
grid: new CutGrid({ kind: "uniform", fileIndex: 0 }),
|
|
79
|
-
video: new VideoOutput({ fileIndex: 0, encode }),
|
|
80
|
-
audio: new AudioOutput({ fileIndex: 0, trackIndex: 0, transcode: true })
|
|
81
|
-
}),
|
|
82
|
-
serving: { mode: "manual", linkMbps: null, keys: [nominal], readyAt: () => true }
|
|
83
|
-
});
|
|
84
|
-
assert.equal(asked.reusedKey, null);
|
|
85
|
-
assert.deepEqual(asked.spec.video.encode.rateControl, softwareRateControlFor({ width: 1280, height: 720, fps: 24, capKbps: 1400 }));
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("a hardware output states no limit, because it is given none", () => {
|
|
89
|
-
assert.equal(decide(HARDWARE).spec.video.encode.rateControl, null);
|
|
90
|
-
assert.ok(decide(HARDWARE).spec.toKey().endsWith("/vbv=-:a=0/0/aac"));
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("a limit above the size's own is refused rather than lowered", () => {
|
|
94
|
-
assert.throws(() => decide(SOFTWARE, nominalKbpsFor({ width: 1280, height: 720 }) + 1), RangeError);
|
|
95
|
-
assert.throws(() => decide(SOFTWARE, 0), RangeError, "and a limit that is not a positive rate is not a limit");
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// Step 11: the viewer's own link decides whether a format may be given at all.
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* A 720p output of a 1080p source for a viewer whose link measured `linkMbps`.
|
|
104
|
-
*
|
|
105
|
-
* @param {{ mode?: "auto" | "manual", linkMbps?: number | null, limits?: number[], encoder?: object, audioLoad?: object | null }} how
|
|
106
|
-
*/
|
|
107
|
-
function decideFor({ mode = "manual", linkMbps = null, limits = null, encoder = SOFTWARE, audioLoad = null } = {}) {
|
|
108
|
-
return decideOutputFormat({
|
|
109
|
-
encodesPicture: true,
|
|
110
|
-
exact: true,
|
|
111
|
-
target: { width: 1280, height: 720 },
|
|
112
|
-
source: { width: 1920, height: 1080, megabitsPerSecond: 8, decode: null },
|
|
113
|
-
fps: 24,
|
|
114
|
-
encoder,
|
|
115
|
-
benchmark: null,
|
|
116
|
-
cost: { decodeModel: null, observedDecodeCostSec: null, requiredSpeed: null },
|
|
117
|
-
chooseBudget: () => null,
|
|
118
|
-
tonemap: false,
|
|
119
|
-
...(limits ? { limitsFor: (frame) => (frame.height === 720 ? limits : [nominalKbpsFor(frame)]) } : {}),
|
|
120
|
-
audioLoad,
|
|
121
|
-
specWith: (encode) => new OutputSpec({
|
|
122
|
-
sourceKey: "torrent:abc",
|
|
123
|
-
segmentFormatId: "fmp4",
|
|
124
|
-
grid: new CutGrid({ kind: "uniform", fileIndex: 0 }),
|
|
125
|
-
video: new VideoOutput({ fileIndex: 0, encode }),
|
|
126
|
-
audio: null
|
|
127
|
-
}),
|
|
128
|
-
serving: { mode, linkMbps, keys: [], readyAt: () => false }
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
test("a size picked by hand keeps its height and takes the highest limit the link admits", () => {
|
|
133
|
-
// 720p nominal peaks at 3.64 Mbit/s; a 3 Mbit/s link admits 2.4 of it.
|
|
134
|
-
const decided = decideFor({ mode: "manual", linkMbps: 3, limits: [2800, 1400] });
|
|
135
|
-
assert.equal(decided.unavailable, null);
|
|
136
|
-
assert.equal(decided.spec.video.encode.height, 720, "the height picked by hand is kept");
|
|
137
|
-
assert.deepEqual(
|
|
138
|
-
decided.spec.video.encode.rateControl,
|
|
139
|
-
softwareRateControlFor({ width: 1280, height: 720, fps: 24, capKbps: 1400 })
|
|
140
|
-
);
|
|
141
|
-
assert.equal(decided.answer.verdict, "fits");
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("AUTO moves to a smaller rung only when that rung's own load is admitted", () => {
|
|
145
|
-
// Nothing at 720p fits 3 Mbit/s with only the nominal limit, and 480p's
|
|
146
|
-
// nominal peak (1.82 Mbit/s) does.
|
|
147
|
-
const decided = decideFor({ mode: "auto", linkMbps: 3 });
|
|
148
|
-
assert.equal(decided.unavailable, null);
|
|
149
|
-
assert.ok(decided.spec.video.encode.height < 720, "a smaller rung");
|
|
150
|
-
assert.ok(decided.answer.admitted, "whose own load the link admits");
|
|
151
|
-
assert.equal(
|
|
152
|
-
decideFor({ mode: "auto", linkMbps: 0.3 }).unavailable?.reason?.length > 0,
|
|
153
|
-
true,
|
|
154
|
-
"and with nothing admitted at any rung, nothing is handed over"
|
|
155
|
-
);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("nothing admitted is answered as unavailable, with the figures, and names no output", () => {
|
|
159
|
-
const decided = decideFor({ mode: "manual", linkMbps: 3 });
|
|
160
|
-
assert.equal(decided.spec, null, "a format known not to fit is never handed over");
|
|
161
|
-
assert.ok(decided.unavailable.reason.length > 0);
|
|
162
|
-
assert.equal(decided.unavailable.figures.verdict, "does not fit");
|
|
163
|
-
assert.equal(decided.unavailable.figures.linkMbps, 3);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("a hardware output is unavailable against a measured link, and given while nothing measured it", () => {
|
|
167
|
-
assert.equal(decideFor({ encoder: HARDWARE, linkMbps: 50 }).unavailable.figures.verdict, "no safe bound");
|
|
168
|
-
assert.equal(decideFor({ encoder: HARDWARE, linkMbps: null }).answer.verdict, "no measurement");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("the soundtrack the viewer hears counts toward what their link carries", () => {
|
|
172
|
-
// 720p nominal peaks at 3.64 Mbit/s. A 4.6 Mbit/s link admits 3.68 of it:
|
|
173
|
-
// enough for the picture, not for the picture and 128 kbit/s of sound.
|
|
174
|
-
const linkMbps = 4.6;
|
|
175
|
-
assert.equal(decideFor({ mode: "manual", linkMbps }).answer.verdict, "fits");
|
|
176
|
-
assert.ok(
|
|
177
|
-
decideFor({ mode: "manual", linkMbps, audioLoad: { mbps: 0.128, peakClass: "known" } }).unavailable,
|
|
178
|
-
"with the sound it no longer fits"
|
|
179
|
-
);
|
|
180
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file The bitrate limit and the level are decided with the rest of an
|
|
3
|
+
* output's format, before it is named — roadmap item 97, step 10.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here starts ffmpeg, a torrent or a worker: the decision is a pure
|
|
6
|
+
* function of values.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { decideOutputFormat } from "../services/encode/quality/output-format.js";
|
|
12
|
+
import { nominalKbpsFor, softwareRateControlFor } from "../services/encode/args.js";
|
|
13
|
+
import { AudioOutput, CutGrid, OutputSpec, VideoOutput } from "../services/encode/output/OutputSpec.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A 720p output asked for exactly, from a 1080p source, on the encoder given.
|
|
17
|
+
*
|
|
18
|
+
* @param {{ kind: string, name: string }} encoder
|
|
19
|
+
* @param {number | null} [capKbps]
|
|
20
|
+
* @returns {ReturnType<typeof decideOutputFormat>}
|
|
21
|
+
*/
|
|
22
|
+
function decide(encoder, capKbps = null) {
|
|
23
|
+
return decideOutputFormat({
|
|
24
|
+
encodesPicture: true,
|
|
25
|
+
exact: true,
|
|
26
|
+
target: { width: 1280, height: 720 },
|
|
27
|
+
source: { width: 1920, height: 1080, megabitsPerSecond: 8, decode: null },
|
|
28
|
+
fps: 24,
|
|
29
|
+
encoder,
|
|
30
|
+
benchmark: null,
|
|
31
|
+
cost: { decodeModel: null, observedDecodeCostSec: null, requiredSpeed: null },
|
|
32
|
+
chooseBudget: () => null,
|
|
33
|
+
tonemap: false,
|
|
34
|
+
capKbps,
|
|
35
|
+
specWith: (encode) => new OutputSpec({
|
|
36
|
+
sourceKey: "torrent:abc",
|
|
37
|
+
segmentFormatId: "fmp4",
|
|
38
|
+
grid: new CutGrid({ kind: "uniform", fileIndex: 0 }),
|
|
39
|
+
video: new VideoOutput({ fileIndex: 0, encode }),
|
|
40
|
+
audio: new AudioOutput({ fileIndex: 0, trackIndex: 0, transcode: true })
|
|
41
|
+
}),
|
|
42
|
+
serving: { mode: "manual", linkMbps: null, keys: [], readyAt: () => false }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SOFTWARE = { kind: "software", name: "libx264" };
|
|
47
|
+
const HARDWARE = { kind: "nvenc", name: "h264_nvenc" };
|
|
48
|
+
|
|
49
|
+
test("a software output is bounded at its size's nominal rate unless a lower limit is asked for", () => {
|
|
50
|
+
const plain = decide(SOFTWARE).spec.video.encode.rateControl;
|
|
51
|
+
const lower = decide(SOFTWARE, 1400).spec.video.encode.rateControl;
|
|
52
|
+
|
|
53
|
+
assert.deepEqual(plain, softwareRateControlFor({ width: 1280, height: 720, fps: 24 }));
|
|
54
|
+
assert.deepEqual(lower, softwareRateControlFor({ width: 1280, height: 720, fps: 24, capKbps: 1400 }));
|
|
55
|
+
assert.equal(lower.level, plain.level, "both are declared at the nominal output's level");
|
|
56
|
+
assert.notEqual(decide(SOFTWARE).spec.toKey(), decide(SOFTWARE, 1400).spec.toKey(), "and are two outputs");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("a limit named in the request is not answered by an output of another limit already here", () => {
|
|
60
|
+
// The nominal 720p output exists and is ready, and it suits this viewer —
|
|
61
|
+
// which is exactly what a move to a lower limit is for leaving.
|
|
62
|
+
const nominal = decide(SOFTWARE).spec.toKey();
|
|
63
|
+
const asked = decideOutputFormat({
|
|
64
|
+
encodesPicture: true,
|
|
65
|
+
exact: true,
|
|
66
|
+
target: { width: 1280, height: 720 },
|
|
67
|
+
source: { width: 1920, height: 1080, megabitsPerSecond: 8, decode: null },
|
|
68
|
+
fps: 24,
|
|
69
|
+
encoder: SOFTWARE,
|
|
70
|
+
benchmark: null,
|
|
71
|
+
cost: { decodeModel: null, observedDecodeCostSec: null, requiredSpeed: null },
|
|
72
|
+
chooseBudget: () => null,
|
|
73
|
+
tonemap: false,
|
|
74
|
+
capKbps: 1400,
|
|
75
|
+
specWith: (encode) => new OutputSpec({
|
|
76
|
+
sourceKey: "torrent:abc",
|
|
77
|
+
segmentFormatId: "fmp4",
|
|
78
|
+
grid: new CutGrid({ kind: "uniform", fileIndex: 0 }),
|
|
79
|
+
video: new VideoOutput({ fileIndex: 0, encode }),
|
|
80
|
+
audio: new AudioOutput({ fileIndex: 0, trackIndex: 0, transcode: true })
|
|
81
|
+
}),
|
|
82
|
+
serving: { mode: "manual", linkMbps: null, keys: [nominal], readyAt: () => true }
|
|
83
|
+
});
|
|
84
|
+
assert.equal(asked.reusedKey, null);
|
|
85
|
+
assert.deepEqual(asked.spec.video.encode.rateControl, softwareRateControlFor({ width: 1280, height: 720, fps: 24, capKbps: 1400 }));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("a hardware output states no limit, because it is given none", () => {
|
|
89
|
+
assert.equal(decide(HARDWARE).spec.video.encode.rateControl, null);
|
|
90
|
+
assert.ok(decide(HARDWARE).spec.toKey().endsWith("/vbv=-:a=0/0/aac"));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("a limit above the size's own is refused rather than lowered", () => {
|
|
94
|
+
assert.throws(() => decide(SOFTWARE, nominalKbpsFor({ width: 1280, height: 720 }) + 1), RangeError);
|
|
95
|
+
assert.throws(() => decide(SOFTWARE, 0), RangeError, "and a limit that is not a positive rate is not a limit");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Step 11: the viewer's own link decides whether a format may be given at all.
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A 720p output of a 1080p source for a viewer whose link measured `linkMbps`.
|
|
104
|
+
*
|
|
105
|
+
* @param {{ mode?: "auto" | "manual", linkMbps?: number | null, limits?: number[], encoder?: object, audioLoad?: object | null }} how
|
|
106
|
+
*/
|
|
107
|
+
function decideFor({ mode = "manual", linkMbps = null, limits = null, encoder = SOFTWARE, audioLoad = null } = {}) {
|
|
108
|
+
return decideOutputFormat({
|
|
109
|
+
encodesPicture: true,
|
|
110
|
+
exact: true,
|
|
111
|
+
target: { width: 1280, height: 720 },
|
|
112
|
+
source: { width: 1920, height: 1080, megabitsPerSecond: 8, decode: null },
|
|
113
|
+
fps: 24,
|
|
114
|
+
encoder,
|
|
115
|
+
benchmark: null,
|
|
116
|
+
cost: { decodeModel: null, observedDecodeCostSec: null, requiredSpeed: null },
|
|
117
|
+
chooseBudget: () => null,
|
|
118
|
+
tonemap: false,
|
|
119
|
+
...(limits ? { limitsFor: (frame) => (frame.height === 720 ? limits : [nominalKbpsFor(frame)]) } : {}),
|
|
120
|
+
audioLoad,
|
|
121
|
+
specWith: (encode) => new OutputSpec({
|
|
122
|
+
sourceKey: "torrent:abc",
|
|
123
|
+
segmentFormatId: "fmp4",
|
|
124
|
+
grid: new CutGrid({ kind: "uniform", fileIndex: 0 }),
|
|
125
|
+
video: new VideoOutput({ fileIndex: 0, encode }),
|
|
126
|
+
audio: null
|
|
127
|
+
}),
|
|
128
|
+
serving: { mode, linkMbps, keys: [], readyAt: () => false }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
test("a size picked by hand keeps its height and takes the highest limit the link admits", () => {
|
|
133
|
+
// 720p nominal peaks at 3.64 Mbit/s; a 3 Mbit/s link admits 2.4 of it.
|
|
134
|
+
const decided = decideFor({ mode: "manual", linkMbps: 3, limits: [2800, 1400] });
|
|
135
|
+
assert.equal(decided.unavailable, null);
|
|
136
|
+
assert.equal(decided.spec.video.encode.height, 720, "the height picked by hand is kept");
|
|
137
|
+
assert.deepEqual(
|
|
138
|
+
decided.spec.video.encode.rateControl,
|
|
139
|
+
softwareRateControlFor({ width: 1280, height: 720, fps: 24, capKbps: 1400 })
|
|
140
|
+
);
|
|
141
|
+
assert.equal(decided.answer.verdict, "fits");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("AUTO moves to a smaller rung only when that rung's own load is admitted", () => {
|
|
145
|
+
// Nothing at 720p fits 3 Mbit/s with only the nominal limit, and 480p's
|
|
146
|
+
// nominal peak (1.82 Mbit/s) does.
|
|
147
|
+
const decided = decideFor({ mode: "auto", linkMbps: 3 });
|
|
148
|
+
assert.equal(decided.unavailable, null);
|
|
149
|
+
assert.ok(decided.spec.video.encode.height < 720, "a smaller rung");
|
|
150
|
+
assert.ok(decided.answer.admitted, "whose own load the link admits");
|
|
151
|
+
assert.equal(
|
|
152
|
+
decideFor({ mode: "auto", linkMbps: 0.3 }).unavailable?.reason?.length > 0,
|
|
153
|
+
true,
|
|
154
|
+
"and with nothing admitted at any rung, nothing is handed over"
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("nothing admitted is answered as unavailable, with the figures, and names no output", () => {
|
|
159
|
+
const decided = decideFor({ mode: "manual", linkMbps: 3 });
|
|
160
|
+
assert.equal(decided.spec, null, "a format known not to fit is never handed over");
|
|
161
|
+
assert.ok(decided.unavailable.reason.length > 0);
|
|
162
|
+
assert.equal(decided.unavailable.figures.verdict, "does not fit");
|
|
163
|
+
assert.equal(decided.unavailable.figures.linkMbps, 3);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("a hardware output is unavailable against a measured link, and given while nothing measured it", () => {
|
|
167
|
+
assert.equal(decideFor({ encoder: HARDWARE, linkMbps: 50 }).unavailable.figures.verdict, "no safe bound");
|
|
168
|
+
assert.equal(decideFor({ encoder: HARDWARE, linkMbps: null }).answer.verdict, "no measurement");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("the soundtrack the viewer hears counts toward what their link carries", () => {
|
|
172
|
+
// 720p nominal peaks at 3.64 Mbit/s. A 4.6 Mbit/s link admits 3.68 of it:
|
|
173
|
+
// enough for the picture, not for the picture and 128 kbit/s of sound.
|
|
174
|
+
const linkMbps = 4.6;
|
|
175
|
+
assert.equal(decideFor({ mode: "manual", linkMbps }).answer.verdict, "fits");
|
|
176
|
+
assert.ok(
|
|
177
|
+
decideFor({ mode: "manual", linkMbps, audioLoad: { mbps: 0.128, peakClass: "known" } }).unavailable,
|
|
178
|
+
"with the sound it no longer fits"
|
|
179
|
+
);
|
|
180
|
+
});
|