@torrent-tv/proxy 2.89.7 → 2.89.9
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 +363 -338
- package/CHANGELOG.md +2345 -2335
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/calibration/audio/NOTICE.md +19 -19
- package/assets/calibration/audio/manifest.json +98 -98
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +412 -412
- package/docs/browser-proxy-contract.md +155 -155
- package/docs/container-architecture.md +273 -273
- 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 +113 -113
- 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 +51 -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 -106
- 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/build-audio-samples.mjs +31 -31
- 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 +36 -12
- 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 +784 -784
- 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/audio-calibration.js +67 -67
- package/services/encode/audio-work-rate.js +15 -15
- 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 -116
- 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/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 +24 -24
- 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 +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 +757 -757
- 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/init-compat.js +324 -324
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +202 -202
- package/services/media/KeyframeTables.js +356 -356
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +592 -592
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +299 -299
- package/services/media/container/AviContainer.js +268 -268
- package/services/media/container/Container.js +632 -632
- package/services/media/container/ContainerFactory.js +38 -38
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1031 -1031
- package/services/media/container/Mp4Container.js +1354 -1354
- package/services/media/container/SubtitleFileContainer.js +339 -339
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/container/ebml-stream.js +174 -174
- package/services/media/container/matroska-clusters.js +1165 -1165
- package/services/media/container/unavailable.js +77 -77
- 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/ContainerTrack.js +84 -84
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +295 -295
- 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/piece-store/shared-piece-store.js +1937 -1937
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1294 -1294
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +382 -382
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/episode-naming.js +182 -182
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +264 -264
- package/services/torrent/torrent-pool.js +2688 -2688
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +836 -836
- 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 +231 -231
- 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 +1137 -1137
- package/services/transport/data-channel-handler.js +1406 -1373
- package/services/transport/delivery-probe.js +798 -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 +421 -421
- 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-calibration.test.js +23 -23
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +762 -762
- 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/coalesce.test.js +39 -39
- 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 +233 -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 +162 -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 +475 -475
- package/test/delivery-probe-field.test.js +173 -0
- package/test/delivery-probe.test.js +382 -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 +20 -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/episode-naming.test.js +131 -131
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fixtures/audio-frame-boundary.json +1 -1
- package/test/fmp4-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 +36 -38
- package/test/held-read-ownership.test.js +77 -77
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +248 -240
- package/test/helpers/held-file.js +77 -77
- package/test/helpers/manager.js +62 -62
- package/test/helpers/matroska-file.js +296 -296
- 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 +123 -123
- 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-cluster-discovery.test.js +323 -323
- 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-output-one-session.test.js +273 -273
- 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 +92 -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-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/quality-variants.test.js +1274 -1270
- package/test/rate-control-in-the-output.test.js +181 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/reads-not-yet-arrived.test.js +218 -218
- 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-closed-on-both-branches.test.js +114 -114
- 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 +222 -222
- package/test/segment-serve-wiring.test.js +481 -481
- 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-existing-output.test.js +111 -111
- 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-files-route.test.js +113 -113
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +237 -237
- 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 +385 -385
- 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-metadata.test.js +15 -15
- package/test/subtitle-plan-waits.test.js +125 -125
- package/test/subtitle-track-numbering.test.js +391 -391
- 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/two-viewers-one-picture.test.js +508 -508
- 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 +92 -92
- package/test/viewer-starts-on-map.test.js +27 -27
- package/test/viewer-states.test.js +192 -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/whole-file-held-read.test.js +30 -30
- 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/coalesce.js +41 -41
- 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,87 +1,87 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Where one encoder run's time went: working, waiting for its input, or
|
|
3
|
-
* stopped.
|
|
4
|
-
*
|
|
5
|
-
* A speed read as "film made per second of clock" measures the swarm as well
|
|
6
|
-
* as the machine whenever the input runs dry: field 2026-10-01, a copy waited
|
|
7
|
-
* 32.93 s and 43.91 s for two pieces and its speed read 0.21x, which the
|
|
8
|
-
* forecast then carried over the whole remaining film while it charged the
|
|
9
|
-
* same download separately. So the clock is split, from two facts that are
|
|
10
|
-
* observed and not inferred:
|
|
11
|
-
*
|
|
12
|
-
* 1. the input read waited — the stream route says when it starts and stops
|
|
13
|
-
* waiting for bytes of this run's input;
|
|
14
|
-
* 2. the process was stopped — the run says when it sends `SIGSTOP` and
|
|
15
|
-
* `SIGCONT`.
|
|
16
|
-
*
|
|
17
|
-
* Time during which either holds is idle; the rest is the run's own work. Two
|
|
18
|
-
* inputs that wait at once, or a wait while the process is stopped, are idle
|
|
19
|
-
* once. What is not separated is what the encoder does with bytes it already
|
|
20
|
-
* holds while its read waits; that is bounded by its input buffer, which is
|
|
21
|
-
* the one error this split has.
|
|
22
|
-
*/
|
|
23
|
-
export class RunClock {
|
|
24
|
-
/** @type {() => number} */
|
|
25
|
-
#now;
|
|
26
|
-
#startedAt;
|
|
27
|
-
/** Idle causes in force now: input reads waiting, plus one while stopped. */
|
|
28
|
-
#idleCauses = 0;
|
|
29
|
-
#idleSince = 0;
|
|
30
|
-
#idleMs = 0;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {{ now?: () => number }} [options]
|
|
34
|
-
*/
|
|
35
|
-
constructor({ now = Date.now } = {}) {
|
|
36
|
-
this.#now = now;
|
|
37
|
-
this.#startedAt = now();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
#idleBegins() {
|
|
41
|
-
if (this.#idleCauses === 0) {
|
|
42
|
-
this.#idleSince = this.#now();
|
|
43
|
-
}
|
|
44
|
-
this.#idleCauses += 1;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
#idleEnds() {
|
|
48
|
-
if (this.#idleCauses === 0) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
this.#idleCauses -= 1;
|
|
52
|
-
if (this.#idleCauses === 0) {
|
|
53
|
-
this.#idleMs += this.#now() - this.#idleSince;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** An input read of this run starts waiting for bytes. */
|
|
58
|
-
inputWaitBegins() {
|
|
59
|
-
this.#idleBegins();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** An input read of this run has its bytes. */
|
|
63
|
-
inputWaitEnds() {
|
|
64
|
-
this.#idleEnds();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** The process is stopped. */
|
|
68
|
-
stopped() {
|
|
69
|
-
this.#idleBegins();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** The process runs again. */
|
|
73
|
-
continued() {
|
|
74
|
-
this.#idleEnds();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Milliseconds of the run's own work so far.
|
|
79
|
-
*
|
|
80
|
-
* @param {number} [at]
|
|
81
|
-
* @returns {number}
|
|
82
|
-
*/
|
|
83
|
-
workingMs(at = this.#now()) {
|
|
84
|
-
const idleNow = this.#idleCauses > 0 ? at - this.#idleSince : 0;
|
|
85
|
-
return Math.max(0, at - this.#startedAt - this.#idleMs - idleNow);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file Where one encoder run's time went: working, waiting for its input, or
|
|
3
|
+
* stopped.
|
|
4
|
+
*
|
|
5
|
+
* A speed read as "film made per second of clock" measures the swarm as well
|
|
6
|
+
* as the machine whenever the input runs dry: field 2026-10-01, a copy waited
|
|
7
|
+
* 32.93 s and 43.91 s for two pieces and its speed read 0.21x, which the
|
|
8
|
+
* forecast then carried over the whole remaining film while it charged the
|
|
9
|
+
* same download separately. So the clock is split, from two facts that are
|
|
10
|
+
* observed and not inferred:
|
|
11
|
+
*
|
|
12
|
+
* 1. the input read waited — the stream route says when it starts and stops
|
|
13
|
+
* waiting for bytes of this run's input;
|
|
14
|
+
* 2. the process was stopped — the run says when it sends `SIGSTOP` and
|
|
15
|
+
* `SIGCONT`.
|
|
16
|
+
*
|
|
17
|
+
* Time during which either holds is idle; the rest is the run's own work. Two
|
|
18
|
+
* inputs that wait at once, or a wait while the process is stopped, are idle
|
|
19
|
+
* once. What is not separated is what the encoder does with bytes it already
|
|
20
|
+
* holds while its read waits; that is bounded by its input buffer, which is
|
|
21
|
+
* the one error this split has.
|
|
22
|
+
*/
|
|
23
|
+
export class RunClock {
|
|
24
|
+
/** @type {() => number} */
|
|
25
|
+
#now;
|
|
26
|
+
#startedAt;
|
|
27
|
+
/** Idle causes in force now: input reads waiting, plus one while stopped. */
|
|
28
|
+
#idleCauses = 0;
|
|
29
|
+
#idleSince = 0;
|
|
30
|
+
#idleMs = 0;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {{ now?: () => number }} [options]
|
|
34
|
+
*/
|
|
35
|
+
constructor({ now = Date.now } = {}) {
|
|
36
|
+
this.#now = now;
|
|
37
|
+
this.#startedAt = now();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#idleBegins() {
|
|
41
|
+
if (this.#idleCauses === 0) {
|
|
42
|
+
this.#idleSince = this.#now();
|
|
43
|
+
}
|
|
44
|
+
this.#idleCauses += 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#idleEnds() {
|
|
48
|
+
if (this.#idleCauses === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.#idleCauses -= 1;
|
|
52
|
+
if (this.#idleCauses === 0) {
|
|
53
|
+
this.#idleMs += this.#now() - this.#idleSince;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** An input read of this run starts waiting for bytes. */
|
|
58
|
+
inputWaitBegins() {
|
|
59
|
+
this.#idleBegins();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** An input read of this run has its bytes. */
|
|
63
|
+
inputWaitEnds() {
|
|
64
|
+
this.#idleEnds();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The process is stopped. */
|
|
68
|
+
stopped() {
|
|
69
|
+
this.#idleBegins();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The process runs again. */
|
|
73
|
+
continued() {
|
|
74
|
+
this.#idleEnds();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Milliseconds of the run's own work so far.
|
|
79
|
+
*
|
|
80
|
+
* @param {number} [at]
|
|
81
|
+
* @returns {number}
|
|
82
|
+
*/
|
|
83
|
+
workingMs(at = this.#now()) {
|
|
84
|
+
const idleNow = this.#idleCauses > 0 ? at - this.#idleSince : 0;
|
|
85
|
+
return Math.max(0, at - this.#startedAt - this.#idleMs - idleNow);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Progress reported by one encoder run, placed on the source timeline.
|
|
3
|
-
*
|
|
4
|
-
* ffmpeg reports time relative to the start of each run on both the re-encode
|
|
5
|
-
* and copy branches. `-output_ts_offset` and `-copyts` change muxed timestamps,
|
|
6
|
-
* not the `-progress` clock. Every reading is therefore rebased by this run's
|
|
7
|
-
* own start, never by an output-wide value shared with another run.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
function parseTimestamp(value) {
|
|
11
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
const parts = value.split(":");
|
|
15
|
-
if (parts.length !== 3) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
const [hours, minutes, seconds] = parts.map(Number);
|
|
19
|
-
return [hours, minutes, seconds].every(Number.isFinite)
|
|
20
|
-
? hours * 3600 + minutes * 60 + seconds
|
|
21
|
-
: null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class RunProgress {
|
|
25
|
-
constructor({ startSeconds = 0, totalSeconds = null, now = Date.now } = {}) {
|
|
26
|
-
this.startPositionSeconds = Number.isFinite(startSeconds) ? Math.max(0, startSeconds) : 0;
|
|
27
|
-
this.processedSeconds = this.startPositionSeconds;
|
|
28
|
-
this.totalSeconds = Number.isFinite(totalSeconds) && totalSeconds > 0 ? totalSeconds : null;
|
|
29
|
-
this.percent = null;
|
|
30
|
-
this.remainingSeconds = this.totalSeconds;
|
|
31
|
-
this.updatedAt = now();
|
|
32
|
-
this.lastLoggedAt = 0;
|
|
33
|
-
this.now = now;
|
|
34
|
-
this.#derive();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
note({ processedSeconds = null, outTime }) {
|
|
38
|
-
const relative = Number.isFinite(processedSeconds)
|
|
39
|
-
? processedSeconds
|
|
40
|
-
: parseTimestamp(outTime);
|
|
41
|
-
if (relative !== null) {
|
|
42
|
-
this.processedSeconds = this.startPositionSeconds + Math.max(0, relative);
|
|
43
|
-
}
|
|
44
|
-
this.updatedAt = this.now();
|
|
45
|
-
this.#derive();
|
|
46
|
-
return this.snapshot();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
shouldLog(everyMs) {
|
|
50
|
-
if (this.percent === null || this.updatedAt - this.lastLoggedAt < everyMs) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
this.lastLoggedAt = this.updatedAt;
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
snapshot() {
|
|
58
|
-
return {
|
|
59
|
-
processedSeconds: this.processedSeconds,
|
|
60
|
-
startPositionSeconds: this.startPositionSeconds,
|
|
61
|
-
totalSeconds: this.totalSeconds,
|
|
62
|
-
percent: this.percent,
|
|
63
|
-
remainingSeconds: this.remainingSeconds,
|
|
64
|
-
updatedAt: this.updatedAt
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#derive() {
|
|
69
|
-
if (this.totalSeconds === null) {
|
|
70
|
-
this.percent = null;
|
|
71
|
-
this.remainingSeconds = null;
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const span = Math.max(1, this.totalSeconds - this.startPositionSeconds);
|
|
75
|
-
const processed = Math.max(0, this.processedSeconds - this.startPositionSeconds);
|
|
76
|
-
this.percent = Math.max(0, Math.min(100, (processed / span) * 100));
|
|
77
|
-
this.remainingSeconds = Math.max(0, this.totalSeconds - this.processedSeconds);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file Progress reported by one encoder run, placed on the source timeline.
|
|
3
|
+
*
|
|
4
|
+
* ffmpeg reports time relative to the start of each run on both the re-encode
|
|
5
|
+
* and copy branches. `-output_ts_offset` and `-copyts` change muxed timestamps,
|
|
6
|
+
* not the `-progress` clock. Every reading is therefore rebased by this run's
|
|
7
|
+
* own start, never by an output-wide value shared with another run.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function parseTimestamp(value) {
|
|
11
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const parts = value.split(":");
|
|
15
|
+
if (parts.length !== 3) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const [hours, minutes, seconds] = parts.map(Number);
|
|
19
|
+
return [hours, minutes, seconds].every(Number.isFinite)
|
|
20
|
+
? hours * 3600 + minutes * 60 + seconds
|
|
21
|
+
: null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class RunProgress {
|
|
25
|
+
constructor({ startSeconds = 0, totalSeconds = null, now = Date.now } = {}) {
|
|
26
|
+
this.startPositionSeconds = Number.isFinite(startSeconds) ? Math.max(0, startSeconds) : 0;
|
|
27
|
+
this.processedSeconds = this.startPositionSeconds;
|
|
28
|
+
this.totalSeconds = Number.isFinite(totalSeconds) && totalSeconds > 0 ? totalSeconds : null;
|
|
29
|
+
this.percent = null;
|
|
30
|
+
this.remainingSeconds = this.totalSeconds;
|
|
31
|
+
this.updatedAt = now();
|
|
32
|
+
this.lastLoggedAt = 0;
|
|
33
|
+
this.now = now;
|
|
34
|
+
this.#derive();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
note({ processedSeconds = null, outTime }) {
|
|
38
|
+
const relative = Number.isFinite(processedSeconds)
|
|
39
|
+
? processedSeconds
|
|
40
|
+
: parseTimestamp(outTime);
|
|
41
|
+
if (relative !== null) {
|
|
42
|
+
this.processedSeconds = this.startPositionSeconds + Math.max(0, relative);
|
|
43
|
+
}
|
|
44
|
+
this.updatedAt = this.now();
|
|
45
|
+
this.#derive();
|
|
46
|
+
return this.snapshot();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
shouldLog(everyMs) {
|
|
50
|
+
if (this.percent === null || this.updatedAt - this.lastLoggedAt < everyMs) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
this.lastLoggedAt = this.updatedAt;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
snapshot() {
|
|
58
|
+
return {
|
|
59
|
+
processedSeconds: this.processedSeconds,
|
|
60
|
+
startPositionSeconds: this.startPositionSeconds,
|
|
61
|
+
totalSeconds: this.totalSeconds,
|
|
62
|
+
percent: this.percent,
|
|
63
|
+
remainingSeconds: this.remainingSeconds,
|
|
64
|
+
updatedAt: this.updatedAt
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#derive() {
|
|
69
|
+
if (this.totalSeconds === null) {
|
|
70
|
+
this.percent = null;
|
|
71
|
+
this.remainingSeconds = null;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const span = Math.max(1, this.totalSeconds - this.startPositionSeconds);
|
|
75
|
+
const processed = Math.max(0, this.processedSeconds - this.startPositionSeconds);
|
|
76
|
+
this.percent = Math.max(0, Math.min(100, (processed / span) * 100));
|
|
77
|
+
this.remainingSeconds = Math.max(0, this.totalSeconds - this.processedSeconds);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What is wanted of one output, in ITS OWN segment numbers.
|
|
3
|
-
*
|
|
4
|
-
* One map per output and nothing else. The map itself is built once, per film,
|
|
5
|
-
* by the layer that knows where the viewers are; this holds the copy converted
|
|
6
|
-
* into an output's own numbering, because two outputs of one film are cut
|
|
7
|
-
* independently — 454 pieces against 401 on the field file — so the same second
|
|
8
|
-
* is a different number in each.
|
|
9
|
-
*
|
|
10
|
-
* **No viewer appears here.** It used to hold a window per viewer per band and
|
|
11
|
-
* merge them itself, which was the priority layer's work done a second time in
|
|
12
|
-
* the wrong place, and it carried the viewer's name as the key of a claim —
|
|
13
|
-
* against the rule that the encoding and the viewer are not connected at all.
|
|
14
|
-
* What arrives now is already merged and already anonymous.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export class SegmentDemand {
|
|
18
|
-
/** Output address to what is wanted of it. @type {Map<string, object[]>} */
|
|
19
|
-
#maps = new Map();
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* What is wanted of one output, replacing whatever was wanted before.
|
|
23
|
-
*
|
|
24
|
-
* An EMPTY map is a statement and is stored as one: it says nobody is coming
|
|
25
|
-
* anywhere in this output, which is what stops the encoders on it.
|
|
26
|
-
*
|
|
27
|
-
* @param {string} address
|
|
28
|
-
* @param {{ from: number, to: number, priority: number, withinSeconds: number }[]} zones
|
|
29
|
-
*/
|
|
30
|
-
state(address, zones) {
|
|
31
|
-
this.#maps.set(address, Array.isArray(zones) ? zones : []);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* An output nobody has anything to say about any more.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} address
|
|
38
|
-
*/
|
|
39
|
-
forget(address) {
|
|
40
|
-
this.#maps.delete(address);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Every output anything is wanted of.
|
|
45
|
-
*
|
|
46
|
-
* @returns {string[]}
|
|
47
|
-
*/
|
|
48
|
-
addresses() {
|
|
49
|
-
return [...this.#maps.keys()];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* What is wanted of one output.
|
|
54
|
-
*
|
|
55
|
-
* @param {string} address
|
|
56
|
-
* @returns {{ from: number, to: number, priority: number, withinSeconds: number }[]}
|
|
57
|
-
*/
|
|
58
|
-
mapOn(address) {
|
|
59
|
-
return this.#maps.get(address) ?? [];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* What the map says about one segment, and about the map it sits in.
|
|
64
|
-
*
|
|
65
|
-
* Asked by whoever MEASURES how well the map is being served: a wait means
|
|
66
|
-
* one thing at the top rank and another at the bottom, and telling them apart
|
|
67
|
-
* needs both the segment's own rank and the highest rank stated — a rank is a
|
|
68
|
-
* position in this map, not an absolute number.
|
|
69
|
-
*
|
|
70
|
-
* AND AN EMPTY MAP IS NOT AN ANSWER, which is why the top rank comes back even
|
|
71
|
-
* when the segment's own is zero. A top rank of zero says the map has not been
|
|
72
|
-
* built yet — a session created a moment ago, before the first pass — and that
|
|
73
|
-
* is a different thing from "nobody is coming". Conflated, every request
|
|
74
|
-
* behind the run reads as absent for as long as a fresh session has no map.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} address
|
|
77
|
-
* @param {number} index
|
|
78
|
-
* @returns {{ rank: number, topRank: number }} Rank zero for a number in
|
|
79
|
-
* nobody's zone, which is a statement: nothing is coming for it.
|
|
80
|
-
*/
|
|
81
|
-
rankOf(address, index) {
|
|
82
|
-
const zones = this.mapOn(address);
|
|
83
|
-
let rank = 0;
|
|
84
|
-
let topRank = 0;
|
|
85
|
-
for (const zone of zones) {
|
|
86
|
-
if (zone.priority > topRank) {
|
|
87
|
-
topRank = zone.priority;
|
|
88
|
-
}
|
|
89
|
-
if (index >= zone.from && index <= zone.to && zone.priority > rank) {
|
|
90
|
-
rank = zone.priority;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return { rank, topRank };
|
|
94
|
-
}
|
|
95
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file What is wanted of one output, in ITS OWN segment numbers.
|
|
3
|
+
*
|
|
4
|
+
* One map per output and nothing else. The map itself is built once, per film,
|
|
5
|
+
* by the layer that knows where the viewers are; this holds the copy converted
|
|
6
|
+
* into an output's own numbering, because two outputs of one film are cut
|
|
7
|
+
* independently — 454 pieces against 401 on the field file — so the same second
|
|
8
|
+
* is a different number in each.
|
|
9
|
+
*
|
|
10
|
+
* **No viewer appears here.** It used to hold a window per viewer per band and
|
|
11
|
+
* merge them itself, which was the priority layer's work done a second time in
|
|
12
|
+
* the wrong place, and it carried the viewer's name as the key of a claim —
|
|
13
|
+
* against the rule that the encoding and the viewer are not connected at all.
|
|
14
|
+
* What arrives now is already merged and already anonymous.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class SegmentDemand {
|
|
18
|
+
/** Output address to what is wanted of it. @type {Map<string, object[]>} */
|
|
19
|
+
#maps = new Map();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* What is wanted of one output, replacing whatever was wanted before.
|
|
23
|
+
*
|
|
24
|
+
* An EMPTY map is a statement and is stored as one: it says nobody is coming
|
|
25
|
+
* anywhere in this output, which is what stops the encoders on it.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} address
|
|
28
|
+
* @param {{ from: number, to: number, priority: number, withinSeconds: number }[]} zones
|
|
29
|
+
*/
|
|
30
|
+
state(address, zones) {
|
|
31
|
+
this.#maps.set(address, Array.isArray(zones) ? zones : []);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* An output nobody has anything to say about any more.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} address
|
|
38
|
+
*/
|
|
39
|
+
forget(address) {
|
|
40
|
+
this.#maps.delete(address);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every output anything is wanted of.
|
|
45
|
+
*
|
|
46
|
+
* @returns {string[]}
|
|
47
|
+
*/
|
|
48
|
+
addresses() {
|
|
49
|
+
return [...this.#maps.keys()];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* What is wanted of one output.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} address
|
|
56
|
+
* @returns {{ from: number, to: number, priority: number, withinSeconds: number }[]}
|
|
57
|
+
*/
|
|
58
|
+
mapOn(address) {
|
|
59
|
+
return this.#maps.get(address) ?? [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* What the map says about one segment, and about the map it sits in.
|
|
64
|
+
*
|
|
65
|
+
* Asked by whoever MEASURES how well the map is being served: a wait means
|
|
66
|
+
* one thing at the top rank and another at the bottom, and telling them apart
|
|
67
|
+
* needs both the segment's own rank and the highest rank stated — a rank is a
|
|
68
|
+
* position in this map, not an absolute number.
|
|
69
|
+
*
|
|
70
|
+
* AND AN EMPTY MAP IS NOT AN ANSWER, which is why the top rank comes back even
|
|
71
|
+
* when the segment's own is zero. A top rank of zero says the map has not been
|
|
72
|
+
* built yet — a session created a moment ago, before the first pass — and that
|
|
73
|
+
* is a different thing from "nobody is coming". Conflated, every request
|
|
74
|
+
* behind the run reads as absent for as long as a fresh session has no map.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} address
|
|
77
|
+
* @param {number} index
|
|
78
|
+
* @returns {{ rank: number, topRank: number }} Rank zero for a number in
|
|
79
|
+
* nobody's zone, which is a statement: nothing is coming for it.
|
|
80
|
+
*/
|
|
81
|
+
rankOf(address, index) {
|
|
82
|
+
const zones = this.mapOn(address);
|
|
83
|
+
let rank = 0;
|
|
84
|
+
let topRank = 0;
|
|
85
|
+
for (const zone of zones) {
|
|
86
|
+
if (zone.priority > topRank) {
|
|
87
|
+
topRank = zone.priority;
|
|
88
|
+
}
|
|
89
|
+
if (index >= zone.from && index <= zone.to && zone.priority > rank) {
|
|
90
|
+
rank = zone.priority;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { rank, topRank };
|
|
94
|
+
}
|
|
95
|
+
}
|