@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,52 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file A torrent that was destroyed must not be handed to a reader.
|
|
3
|
-
*
|
|
4
|
-
* The worker remembers each source as a promise, and only ever forgot one when
|
|
5
|
-
* the ADD failed. But the pool destroys a torrent that has gone unread for a
|
|
6
|
-
* quarter of an hour, and under disk pressure — clearing its own map, not this
|
|
7
|
-
* one. The promise then went on resolving to a corpse: a destroyed torrent
|
|
8
|
-
* keeps its object and loses its files.
|
|
9
|
-
*
|
|
10
|
-
* What that did to a viewer, measured 2026-08-06 on two sessions in a row: the
|
|
11
|
-
* plan answered from cache in 23 ms, the session was created from cache in
|
|
12
|
-
* 2 ms, nothing waited for metadata because everything believed the torrent was
|
|
13
|
-
* known, and ffmpeg's first read died 130 ms in with `File 0 not found`. Every
|
|
14
|
-
* request for the playlist then answered 500 until the viewer gave up.
|
|
15
|
-
*
|
|
16
|
-
* The rule under test is the whole fix: a handle that cannot be read from is
|
|
17
|
-
* not returned, it is replaced.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import test from "node:test";
|
|
21
|
-
import assert from "node:assert/strict";
|
|
22
|
-
import { isUsableTorrentHandle } from "../services/torrent/worker/handle-state.js";
|
|
23
|
-
|
|
24
|
-
test("a live torrent is usable", () => {
|
|
25
|
-
assert.equal(isUsableTorrentHandle({ destroyed: false, files: [{ name: "a.mkv" }] }), true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("a destroyed torrent is not, even while it still lists files", () => {
|
|
29
|
-
assert.equal(
|
|
30
|
-
isUsableTorrentHandle({ destroyed: true, files: [{ name: "a.mkv" }] }),
|
|
31
|
-
false,
|
|
32
|
-
"WebTorrent's own flag is the earliest signal that a handle is finished"
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("a torrent with no files is not — that is what the reader actually hits", () => {
|
|
37
|
-
assert.equal(
|
|
38
|
-
isUsableTorrentHandle({ destroyed: false, files: [] }),
|
|
39
|
-
false,
|
|
40
|
-
"an empty file list is what produced `File 0 not found` in the field"
|
|
41
|
-
);
|
|
42
|
-
assert.equal(
|
|
43
|
-
isUsableTorrentHandle({ destroyed: false }),
|
|
44
|
-
false,
|
|
45
|
-
"and a handle with no file list at all is the same case"
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("nothing at all is not usable", () => {
|
|
50
|
-
assert.equal(isUsableTorrentHandle(null), false);
|
|
51
|
-
assert.equal(isUsableTorrentHandle(undefined), false);
|
|
52
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file A torrent that was destroyed must not be handed to a reader.
|
|
3
|
+
*
|
|
4
|
+
* The worker remembers each source as a promise, and only ever forgot one when
|
|
5
|
+
* the ADD failed. But the pool destroys a torrent that has gone unread for a
|
|
6
|
+
* quarter of an hour, and under disk pressure — clearing its own map, not this
|
|
7
|
+
* one. The promise then went on resolving to a corpse: a destroyed torrent
|
|
8
|
+
* keeps its object and loses its files.
|
|
9
|
+
*
|
|
10
|
+
* What that did to a viewer, measured 2026-08-06 on two sessions in a row: the
|
|
11
|
+
* plan answered from cache in 23 ms, the session was created from cache in
|
|
12
|
+
* 2 ms, nothing waited for metadata because everything believed the torrent was
|
|
13
|
+
* known, and ffmpeg's first read died 130 ms in with `File 0 not found`. Every
|
|
14
|
+
* request for the playlist then answered 500 until the viewer gave up.
|
|
15
|
+
*
|
|
16
|
+
* The rule under test is the whole fix: a handle that cannot be read from is
|
|
17
|
+
* not returned, it is replaced.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import test from "node:test";
|
|
21
|
+
import assert from "node:assert/strict";
|
|
22
|
+
import { isUsableTorrentHandle } from "../services/torrent/worker/handle-state.js";
|
|
23
|
+
|
|
24
|
+
test("a live torrent is usable", () => {
|
|
25
|
+
assert.equal(isUsableTorrentHandle({ destroyed: false, files: [{ name: "a.mkv" }] }), true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("a destroyed torrent is not, even while it still lists files", () => {
|
|
29
|
+
assert.equal(
|
|
30
|
+
isUsableTorrentHandle({ destroyed: true, files: [{ name: "a.mkv" }] }),
|
|
31
|
+
false,
|
|
32
|
+
"WebTorrent's own flag is the earliest signal that a handle is finished"
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("a torrent with no files is not — that is what the reader actually hits", () => {
|
|
37
|
+
assert.equal(
|
|
38
|
+
isUsableTorrentHandle({ destroyed: false, files: [] }),
|
|
39
|
+
false,
|
|
40
|
+
"an empty file list is what produced `File 0 not found` in the field"
|
|
41
|
+
);
|
|
42
|
+
assert.equal(
|
|
43
|
+
isUsableTorrentHandle({ destroyed: false }),
|
|
44
|
+
false,
|
|
45
|
+
"and a handle with no file list at all is the same case"
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("nothing at all is not usable", () => {
|
|
50
|
+
assert.equal(isUsableTorrentHandle(null), false);
|
|
51
|
+
assert.equal(isUsableTorrentHandle(undefined), false);
|
|
52
|
+
});
|
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What the master playlist declares a variant carries.
|
|
3
|
-
*
|
|
4
|
-
* Measured, and the measurement was available all along: it used to be
|
|
5
|
-
* `height * height * 3.2`, justified by a comment saying no measurement existed
|
|
6
|
-
* before encoding starts. Field 2026-09-08: 3.73 Mbit/s declared for a file
|
|
7
|
-
* carrying 18.4, and the browser sizes its cushion in BYTES from that figure —
|
|
8
|
-
* 120 s asked bought 26 s of film, and the deepest it ever held was 17.1 s.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import test from "node:test";
|
|
12
|
-
import assert from "node:assert/strict";
|
|
13
|
-
import { declaredRates } from "../services/encode/output/rates.js";
|
|
14
|
-
import { bitrateFor } from "../services/encode/output/playlists.js";
|
|
15
|
-
|
|
16
|
-
// The field file: 2 806 246 976 bytes over 20:18, cut into 291 pieces.
|
|
17
|
-
const FIELD = { fileLength: 2_806_246_976, durationSeconds: 1218.73 };
|
|
18
|
-
|
|
19
|
-
test("the average is the file's own length over its duration", () => {
|
|
20
|
-
const { averageBitsPerSecond } = declaredRates(FIELD);
|
|
21
|
-
|
|
22
|
-
// 18.4 Mbit/s, against the 3.73 that was declared.
|
|
23
|
-
assert.ok(
|
|
24
|
-
Math.abs(averageBitsPerSecond / 1_000_000 - 18.42) < 0.01,
|
|
25
|
-
`got ${averageBitsPerSecond}`
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("nothing measured is stated as nothing, not as a guess", () => {
|
|
30
|
-
assert.deepEqual(
|
|
31
|
-
declaredRates({ fileLength: 0, durationSeconds: 0 }),
|
|
32
|
-
{ averageBitsPerSecond: 0, peakOverAverage: 1 }
|
|
33
|
-
);
|
|
34
|
-
assert.deepEqual(
|
|
35
|
-
declaredRates({ fileLength: 100, durationSeconds: 0 }),
|
|
36
|
-
{ averageBitsPerSecond: 0, peakOverAverage: 1 }
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("the peak comes from the biggest piece made and the span it covers", () => {
|
|
41
|
-
// The field peak: 38.4 MB over 4.2 s is 73 Mbit/s, four times the average.
|
|
42
|
-
const boundaries = Array.from({ length: 292 }, (_, index) => index * 4.2);
|
|
43
|
-
const { averageBitsPerSecond, peakOverAverage } = declaredRates({
|
|
44
|
-
...FIELD,
|
|
45
|
-
largest: { index: 2, size: 40_288_299 },
|
|
46
|
-
boundaries
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const peak = averageBitsPerSecond * peakOverAverage;
|
|
50
|
-
assert.ok(Math.abs(peak / 1_000_000 - 76.7) < 0.5, `got ${peak / 1_000_000}Mbit/s`);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("no piece made yet means the peak is not yet measured, so it equals the average", () => {
|
|
54
|
-
// A ratio invented meanwhile is exactly the fabrication this replaced.
|
|
55
|
-
assert.equal(declaredRates(FIELD).peakOverAverage, 1);
|
|
56
|
-
assert.equal(
|
|
57
|
-
declaredRates({ ...FIELD, largest: { index: -1, size: 0 }, boundaries: [0, 4] }).peakOverAverage,
|
|
58
|
-
1
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("the peak is never below the average", () => {
|
|
63
|
-
// A short session whose only piece happens to be a small one must not lower
|
|
64
|
-
// the figure the player sizes its cushion from.
|
|
65
|
-
const boundaries = Array.from({ length: 292 }, (_, index) => index * 4.2);
|
|
66
|
-
assert.equal(
|
|
67
|
-
declaredRates({ ...FIELD, largest: { index: 5, size: 1000 }, boundaries }).peakOverAverage,
|
|
68
|
-
1
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("a smaller picture is declared at the pixel share of the source's rate", () => {
|
|
73
|
-
const measured = 18_420_000;
|
|
74
|
-
|
|
75
|
-
assert.equal(
|
|
76
|
-
bitrateFor({ averageBitsPerSecond: measured, height: 1080, sourceHeight: 1080 }),
|
|
77
|
-
Math.round(measured),
|
|
78
|
-
"the source's own height carries the source's bits"
|
|
79
|
-
);
|
|
80
|
-
assert.equal(
|
|
81
|
-
bitrateFor({ averageBitsPerSecond: measured, height: 2160, sourceHeight: 1080 }),
|
|
82
|
-
Math.round(measured),
|
|
83
|
-
"and a height above it carries no more — there is nothing to upscale from"
|
|
84
|
-
);
|
|
85
|
-
assert.equal(
|
|
86
|
-
bitrateFor({ averageBitsPerSecond: measured, height: 540, sourceHeight: 1080 }),
|
|
87
|
-
Math.round(measured / 4),
|
|
88
|
-
"half the height is a quarter of the pixels"
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("a capped height is declared at its cap, which is exact", () => {
|
|
93
|
-
assert.equal(
|
|
94
|
-
bitrateFor({ averageBitsPerSecond: 18_420_000, height: 720, sourceHeight: 1080, capKbps: 2500 }),
|
|
95
|
-
2_500_000,
|
|
96
|
-
"what we impose is known, not estimated"
|
|
97
|
-
);
|
|
98
|
-
assert.equal(
|
|
99
|
-
bitrateFor({ averageBitsPerSecond: 1_000_000, height: 1080, sourceHeight: 1080, capKbps: 99_000 }),
|
|
100
|
-
1_000_000,
|
|
101
|
-
"and a cap above what the source carries takes nothing away from it"
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("the floor is a floor on what may be declared, not a belief about content", () => {
|
|
106
|
-
assert.equal(
|
|
107
|
-
bitrateFor({ averageBitsPerSecond: 0, height: 1080, sourceHeight: 1080 }),
|
|
108
|
-
400_000,
|
|
109
|
-
"a file whose rate is not known yet"
|
|
110
|
-
);
|
|
111
|
-
assert.equal(
|
|
112
|
-
bitrateFor({ averageBitsPerSecond: 18_420_000, height: 4, sourceHeight: 1080 }),
|
|
113
|
-
400_000,
|
|
114
|
-
"and a variant so small the arithmetic would go under it"
|
|
115
|
-
);
|
|
116
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file What the master playlist declares a variant carries.
|
|
3
|
+
*
|
|
4
|
+
* Measured, and the measurement was available all along: it used to be
|
|
5
|
+
* `height * height * 3.2`, justified by a comment saying no measurement existed
|
|
6
|
+
* before encoding starts. Field 2026-09-08: 3.73 Mbit/s declared for a file
|
|
7
|
+
* carrying 18.4, and the browser sizes its cushion in BYTES from that figure —
|
|
8
|
+
* 120 s asked bought 26 s of film, and the deepest it ever held was 17.1 s.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { declaredRates } from "../services/encode/output/rates.js";
|
|
14
|
+
import { bitrateFor } from "../services/encode/output/playlists.js";
|
|
15
|
+
|
|
16
|
+
// The field file: 2 806 246 976 bytes over 20:18, cut into 291 pieces.
|
|
17
|
+
const FIELD = { fileLength: 2_806_246_976, durationSeconds: 1218.73 };
|
|
18
|
+
|
|
19
|
+
test("the average is the file's own length over its duration", () => {
|
|
20
|
+
const { averageBitsPerSecond } = declaredRates(FIELD);
|
|
21
|
+
|
|
22
|
+
// 18.4 Mbit/s, against the 3.73 that was declared.
|
|
23
|
+
assert.ok(
|
|
24
|
+
Math.abs(averageBitsPerSecond / 1_000_000 - 18.42) < 0.01,
|
|
25
|
+
`got ${averageBitsPerSecond}`
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("nothing measured is stated as nothing, not as a guess", () => {
|
|
30
|
+
assert.deepEqual(
|
|
31
|
+
declaredRates({ fileLength: 0, durationSeconds: 0 }),
|
|
32
|
+
{ averageBitsPerSecond: 0, peakOverAverage: 1 }
|
|
33
|
+
);
|
|
34
|
+
assert.deepEqual(
|
|
35
|
+
declaredRates({ fileLength: 100, durationSeconds: 0 }),
|
|
36
|
+
{ averageBitsPerSecond: 0, peakOverAverage: 1 }
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("the peak comes from the biggest piece made and the span it covers", () => {
|
|
41
|
+
// The field peak: 38.4 MB over 4.2 s is 73 Mbit/s, four times the average.
|
|
42
|
+
const boundaries = Array.from({ length: 292 }, (_, index) => index * 4.2);
|
|
43
|
+
const { averageBitsPerSecond, peakOverAverage } = declaredRates({
|
|
44
|
+
...FIELD,
|
|
45
|
+
largest: { index: 2, size: 40_288_299 },
|
|
46
|
+
boundaries
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const peak = averageBitsPerSecond * peakOverAverage;
|
|
50
|
+
assert.ok(Math.abs(peak / 1_000_000 - 76.7) < 0.5, `got ${peak / 1_000_000}Mbit/s`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("no piece made yet means the peak is not yet measured, so it equals the average", () => {
|
|
54
|
+
// A ratio invented meanwhile is exactly the fabrication this replaced.
|
|
55
|
+
assert.equal(declaredRates(FIELD).peakOverAverage, 1);
|
|
56
|
+
assert.equal(
|
|
57
|
+
declaredRates({ ...FIELD, largest: { index: -1, size: 0 }, boundaries: [0, 4] }).peakOverAverage,
|
|
58
|
+
1
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("the peak is never below the average", () => {
|
|
63
|
+
// A short session whose only piece happens to be a small one must not lower
|
|
64
|
+
// the figure the player sizes its cushion from.
|
|
65
|
+
const boundaries = Array.from({ length: 292 }, (_, index) => index * 4.2);
|
|
66
|
+
assert.equal(
|
|
67
|
+
declaredRates({ ...FIELD, largest: { index: 5, size: 1000 }, boundaries }).peakOverAverage,
|
|
68
|
+
1
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("a smaller picture is declared at the pixel share of the source's rate", () => {
|
|
73
|
+
const measured = 18_420_000;
|
|
74
|
+
|
|
75
|
+
assert.equal(
|
|
76
|
+
bitrateFor({ averageBitsPerSecond: measured, height: 1080, sourceHeight: 1080 }),
|
|
77
|
+
Math.round(measured),
|
|
78
|
+
"the source's own height carries the source's bits"
|
|
79
|
+
);
|
|
80
|
+
assert.equal(
|
|
81
|
+
bitrateFor({ averageBitsPerSecond: measured, height: 2160, sourceHeight: 1080 }),
|
|
82
|
+
Math.round(measured),
|
|
83
|
+
"and a height above it carries no more — there is nothing to upscale from"
|
|
84
|
+
);
|
|
85
|
+
assert.equal(
|
|
86
|
+
bitrateFor({ averageBitsPerSecond: measured, height: 540, sourceHeight: 1080 }),
|
|
87
|
+
Math.round(measured / 4),
|
|
88
|
+
"half the height is a quarter of the pixels"
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("a capped height is declared at its cap, which is exact", () => {
|
|
93
|
+
assert.equal(
|
|
94
|
+
bitrateFor({ averageBitsPerSecond: 18_420_000, height: 720, sourceHeight: 1080, capKbps: 2500 }),
|
|
95
|
+
2_500_000,
|
|
96
|
+
"what we impose is known, not estimated"
|
|
97
|
+
);
|
|
98
|
+
assert.equal(
|
|
99
|
+
bitrateFor({ averageBitsPerSecond: 1_000_000, height: 1080, sourceHeight: 1080, capKbps: 99_000 }),
|
|
100
|
+
1_000_000,
|
|
101
|
+
"and a cap above what the source carries takes nothing away from it"
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("the floor is a floor on what may be declared, not a belief about content", () => {
|
|
106
|
+
assert.equal(
|
|
107
|
+
bitrateFor({ averageBitsPerSecond: 0, height: 1080, sourceHeight: 1080 }),
|
|
108
|
+
400_000,
|
|
109
|
+
"a file whose rate is not known yet"
|
|
110
|
+
);
|
|
111
|
+
assert.equal(
|
|
112
|
+
bitrateFor({ averageBitsPerSecond: 18_420_000, height: 4, sourceHeight: 1080 }),
|
|
113
|
+
400_000,
|
|
114
|
+
"and a variant so small the arithmetic would go under it"
|
|
115
|
+
);
|
|
116
|
+
});
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What the proxy promises the browser must be answerable.
|
|
3
|
-
*
|
|
4
|
-
* The session manager asks the planner's media-info cache which tracks the
|
|
5
|
-
* output will carry. That cache never held the codecs — it holds dimensions,
|
|
6
|
-
* duration, fps, start time and an HDR flag — so the read produced `undefined`
|
|
7
|
-
* for both and the declaration came out `{video: false, audio: false}` for
|
|
8
|
-
* EVERY session since the check existed. Measured 2026-08-11 in the field.
|
|
9
|
-
*
|
|
10
|
-
* Nothing caught it: the writer and the reader each looked correct alone, and
|
|
11
|
-
* no test ever compared the shape of what is stored with the shape of what is
|
|
12
|
-
* read. That comparison is this file's whole job.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import test from "node:test";
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
import { readFileSync } from "node:fs";
|
|
18
|
-
|
|
19
|
-
const planner = readFileSync(new URL("../services/media/playback-planner.js", import.meta.url), "utf8");
|
|
20
|
-
const sessions = readFileSync(new URL("../services/encode/Renditions.js", import.meta.url), "utf8");
|
|
21
|
-
|
|
22
|
-
test("every field the declaration reads is a field the cache stores", () => {
|
|
23
|
-
// What `declaredTracks` reads off the cached media info.
|
|
24
|
-
const read = [...sessions.matchAll(/probed\?\.([A-Za-z]+)/g)].map((match) => match[1]);
|
|
25
|
-
assert.ok(read.length > 0, "the declaration must read something, or it declares nothing");
|
|
26
|
-
|
|
27
|
-
const stored = planner.slice(planner.indexOf("mediaInfoCache.set("));
|
|
28
|
-
for (const field of new Set(read)) {
|
|
29
|
-
assert.ok(
|
|
30
|
-
stored.includes(`${field}:`),
|
|
31
|
-
`the declaration reads "${field}" and the media-info cache never stores it — ` +
|
|
32
|
-
"the read yields undefined and the promise silently becomes false"
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("an empty container track list remains pending for late sidecar metadata", () => {
|
|
38
|
-
assert.match(
|
|
39
|
-
planner,
|
|
40
|
-
/complete: Array\.isArray\(tracks\) && tracks\.length > 0/,
|
|
41
|
-
"ContainerOrchestrator leaves empty track reads uncached while the header is unavailable"
|
|
42
|
-
);
|
|
43
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file What the proxy promises the browser must be answerable.
|
|
3
|
+
*
|
|
4
|
+
* The session manager asks the planner's media-info cache which tracks the
|
|
5
|
+
* output will carry. That cache never held the codecs — it holds dimensions,
|
|
6
|
+
* duration, fps, start time and an HDR flag — so the read produced `undefined`
|
|
7
|
+
* for both and the declaration came out `{video: false, audio: false}` for
|
|
8
|
+
* EVERY session since the check existed. Measured 2026-08-11 in the field.
|
|
9
|
+
*
|
|
10
|
+
* Nothing caught it: the writer and the reader each looked correct alone, and
|
|
11
|
+
* no test ever compared the shape of what is stored with the shape of what is
|
|
12
|
+
* read. That comparison is this file's whole job.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import test from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
|
|
19
|
+
const planner = readFileSync(new URL("../services/media/playback-planner.js", import.meta.url), "utf8");
|
|
20
|
+
const sessions = readFileSync(new URL("../services/encode/Renditions.js", import.meta.url), "utf8");
|
|
21
|
+
|
|
22
|
+
test("every field the declaration reads is a field the cache stores", () => {
|
|
23
|
+
// What `declaredTracks` reads off the cached media info.
|
|
24
|
+
const read = [...sessions.matchAll(/probed\?\.([A-Za-z]+)/g)].map((match) => match[1]);
|
|
25
|
+
assert.ok(read.length > 0, "the declaration must read something, or it declares nothing");
|
|
26
|
+
|
|
27
|
+
const stored = planner.slice(planner.indexOf("mediaInfoCache.set("));
|
|
28
|
+
for (const field of new Set(read)) {
|
|
29
|
+
assert.ok(
|
|
30
|
+
stored.includes(`${field}:`),
|
|
31
|
+
`the declaration reads "${field}" and the media-info cache never stores it — ` +
|
|
32
|
+
"the read yields undefined and the promise silently becomes false"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("an empty container track list remains pending for late sidecar metadata", () => {
|
|
38
|
+
assert.match(
|
|
39
|
+
planner,
|
|
40
|
+
/complete: Array\.isArray\(tracks\) && tracks\.length > 0/,
|
|
41
|
+
"ContainerOrchestrator leaves empty track reads uncached while the header is unavailable"
|
|
42
|
+
);
|
|
43
|
+
});
|