@torrent-tv/proxy 2.88.4 → 2.88.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +2269 -2258
- 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 -0
- package/assets/calibration/audio/aac.m4a +0 -0
- package/assets/calibration/audio/ac3.ac3 +0 -0
- package/assets/calibration/audio/alac.m4a +0 -0
- package/assets/calibration/audio/dts.dts +0 -0
- package/assets/calibration/audio/eac3.eac3 +0 -0
- package/assets/calibration/audio/flac.flac +0 -0
- package/assets/calibration/audio/manifest.json +98 -0
- package/assets/calibration/audio/mp2.mp2 +0 -0
- package/assets/calibration/audio/mp3.mp3 +0 -0
- package/assets/calibration/audio/opus.opus +0 -0
- package/assets/calibration/audio/pcm.wav +0 -0
- package/assets/calibration/audio/truehd.thd +0 -0
- package/assets/calibration/audio/vorbis.ogg +0 -0
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +412 -411
- package/docs/browser-proxy-contract.md +155 -152
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +56 -0
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +380 -380
- 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 -0
- package/scripts/render-run-graph.js +173 -173
- package/server.js +730 -725
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +813 -813
- package/services/encode/EncodeRuns.js +1239 -1235
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +783 -783
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2282 -2282
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +418 -418
- package/services/encode/audio-calibration.js +67 -0
- package/services/encode/audio-work-rate.js +15 -0
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +24 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1167 -1150
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +349 -349
- 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 +755 -754
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +458 -456
- package/services/encode/segment-formats/index.js +85 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +12 -1
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +299 -298
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +609 -609
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1344 -1344
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/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 +1150 -1150
- package/services/server/ViewerRequests.js +786 -760
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/playback-readiness.js +516 -708
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +1020 -995
- 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 -1931
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1255 -1240
- package/services/storage/wire.js +175 -175
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-pool.js +2688 -2675
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -765
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/torrent/worker/worker.js +1129 -1129
- package/services/transport/data-channel-handler.js +1369 -1369
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-calibration.test.js +23 -0
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +162 -162
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-presentation-clock.test.js +162 -147
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +112 -112
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +273 -270
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +401 -401
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +483 -453
- package/test/playback-start-regressions.test.js +99 -99
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1257 -1257
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +352 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +105 -105
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +209 -209
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +504 -504
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Where a file is cut, and which keyframe each cut is.
|
|
3
|
-
*
|
|
4
|
-
* Two answers, not one, and that is the whole point of this file existing.
|
|
5
|
-
*
|
|
6
|
-
* A cut has a time on the PLAYER's clock — 0-based, because a playlist is —
|
|
7
|
-
* and it has a time on the FILE's clock, which is the keyframe the muxer will
|
|
8
|
-
* actually seek to. The two differ by the container's own start time, and a
|
|
9
|
-
* copied picture cannot be cut anywhere but at a real keyframe, so both are
|
|
10
|
-
* needed and neither can be derived from the other after the fact.
|
|
11
|
-
*
|
|
12
|
-
* **Deriving one from the other after the fact is what broke a viewing on
|
|
13
|
-
* 2026-09-05.** The 0-based time was stored, rounded to six places, and the
|
|
14
|
-
* seek then added the start time back and looked the result up in the file's
|
|
15
|
-
* keyframe list by value. That round trip is lossy: a keyframe at 26.234 s in a
|
|
16
|
-
* container starting at 0.083 s comes back as 26.233999999999998, the lookup
|
|
17
|
-
* takes "the keyframe at or before" that, and answers the PREVIOUS one — 8.717 s
|
|
18
|
-
* earlier. Two parts in a quadrillion became one keyframe interval, that
|
|
19
|
-
* interval became a trim, the trim moved every cut of the run backwards by
|
|
20
|
-
* another interval, and the run's files were numbered from #36 while carrying
|
|
21
|
-
* film 17.4 s before what the playlist says #36 holds. The player's video buffer
|
|
22
|
-
* covered the playhead, its audio buffer had a 17.4 s hole across it, the
|
|
23
|
-
* intersection was empty, and the viewer waited two minutes and was told the
|
|
24
|
-
* proxy had sent no video.
|
|
25
|
-
*
|
|
26
|
-
* So the keyframe is carried, by index, from the one place that knows it.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @typedef {object} CutGrid
|
|
31
|
-
* @property {number[]} boundaries - Cut times on the player's clock, 0-based,
|
|
32
|
-
* ascending, one more than there are segments.
|
|
33
|
-
* @property {number[]} sourceTimes - The same cuts on the FILE's clock: for a
|
|
34
|
-
* keyframe grid, the keyframe itself, exactly as the container stated it.
|
|
35
|
-
* Same length as `boundaries`, so an index names both.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Cut a file into segments.
|
|
40
|
-
*
|
|
41
|
-
* On a keyframe grid the cuts are the container's own keyframes, thinned so
|
|
42
|
-
* that no segment is much shorter than asked for; the first cut is the start of
|
|
43
|
-
* the file and the last is its end, neither of which need be a keyframe.
|
|
44
|
-
*
|
|
45
|
-
* On a uniform grid the cuts are multiples of the segment length, and the two
|
|
46
|
-
* clocks are the same clock: a re-encode places its own keyframes and owes the
|
|
47
|
-
* container's start time nothing.
|
|
48
|
-
*
|
|
49
|
-
* @param {object} params
|
|
50
|
-
* @param {boolean} params.useKeyframeGrid
|
|
51
|
-
* @param {number} params.durationSeconds
|
|
52
|
-
* @param {number} params.segDur - The segment length asked for.
|
|
53
|
-
* @param {number[]} [params.keyframeTimes] - On the file's own clock.
|
|
54
|
-
* @param {number} [params.startTime] - The container's start time.
|
|
55
|
-
* @returns {CutGrid}
|
|
56
|
-
*/
|
|
57
|
-
export function computeCutGrid({ useKeyframeGrid, durationSeconds, segDur, keyframeTimes, startTime }) {
|
|
58
|
-
const total = Number.isFinite(durationSeconds) && durationSeconds > 0 ? durationSeconds : 0;
|
|
59
|
-
const step = Number.isFinite(segDur) && segDur > 0 ? segDur : 4;
|
|
60
|
-
const base = Number.isFinite(startTime) ? startTime : 0;
|
|
61
|
-
const uniform = () => {
|
|
62
|
-
const boundaries = [];
|
|
63
|
-
for (let cutAt = 0; cutAt < total - 0.001; cutAt += step) {
|
|
64
|
-
boundaries.push(Number(cutAt.toFixed(6)));
|
|
65
|
-
}
|
|
66
|
-
boundaries.push(total);
|
|
67
|
-
// One clock: nothing here is a keyframe of the source, so nothing is owed
|
|
68
|
-
// the container's start time either.
|
|
69
|
-
return { boundaries, sourceTimes: [...boundaries] };
|
|
70
|
-
};
|
|
71
|
-
if (!useKeyframeGrid || !Array.isArray(keyframeTimes) || keyframeTimes.length === 0 || total <= 0) {
|
|
72
|
-
return uniform();
|
|
73
|
-
}
|
|
74
|
-
const kept = keyframeTimes
|
|
75
|
-
.filter((time) => Number.isFinite(time))
|
|
76
|
-
.map((time) => ({ source: time, published: time - base }))
|
|
77
|
-
// THE END IS MEASURED THE SAME WAY AS EVERY OTHER CUT. A keyframe nearer to
|
|
78
|
-
// the end than one segment leaves a tail too short to be a segment, and the
|
|
79
|
-
// rule below — no cut closer than a step to the one before it — never looks
|
|
80
|
-
// at the end at all. It used to be guarded by fifty milliseconds, a number
|
|
81
|
-
// from nowhere: field 2026-09-11, a keyframe 160 ms before the end of a
|
|
82
|
-
// 54-minute film passed it and left segment #541 lasting 0.16 s. The sound
|
|
83
|
-
// has no data in such a tail, so its run made 541 segments where the picture
|
|
84
|
-
// made 542, was marked short, and the repair that followed was handed a
|
|
85
|
-
// start later than its own end — 190 bytes that are not a fragment, and a
|
|
86
|
-
// viewer held 23 s at the last minute of the film for a 404.
|
|
87
|
-
.filter((cut) => cut.published >= -0.001 && cut.published < total - step)
|
|
88
|
-
.sort((left, right) => left.published - right.published);
|
|
89
|
-
// The first cut is the start of the file, whatever the container's own clock
|
|
90
|
-
// says that is; the last is its end. Neither is a keyframe, and a run never
|
|
91
|
-
// seeks to either — index 0 is served without a seek at all.
|
|
92
|
-
const boundaries = [0];
|
|
93
|
-
const sourceTimes = [base];
|
|
94
|
-
for (const cut of kept) {
|
|
95
|
-
if (cut.published >= boundaries[boundaries.length - 1] + step - 0.05) {
|
|
96
|
-
boundaries.push(Number(cut.published.toFixed(6)));
|
|
97
|
-
// NOT the rounded value with the base added back. This is the number the
|
|
98
|
-
// container stated, and it is what the seek must ask for.
|
|
99
|
-
sourceTimes.push(cut.source);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
boundaries.push(total);
|
|
103
|
-
sourceTimes.push(total + base);
|
|
104
|
-
// A degenerate index — one keyframe, or none inside the film — is no grid at
|
|
105
|
-
// all, and an even one serves better than a table with a single entry.
|
|
106
|
-
return boundaries.length >= 2 ? { boundaries, sourceTimes } : uniform();
|
|
107
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file Where a file is cut, and which keyframe each cut is.
|
|
3
|
+
*
|
|
4
|
+
* Two answers, not one, and that is the whole point of this file existing.
|
|
5
|
+
*
|
|
6
|
+
* A cut has a time on the PLAYER's clock — 0-based, because a playlist is —
|
|
7
|
+
* and it has a time on the FILE's clock, which is the keyframe the muxer will
|
|
8
|
+
* actually seek to. The two differ by the container's own start time, and a
|
|
9
|
+
* copied picture cannot be cut anywhere but at a real keyframe, so both are
|
|
10
|
+
* needed and neither can be derived from the other after the fact.
|
|
11
|
+
*
|
|
12
|
+
* **Deriving one from the other after the fact is what broke a viewing on
|
|
13
|
+
* 2026-09-05.** The 0-based time was stored, rounded to six places, and the
|
|
14
|
+
* seek then added the start time back and looked the result up in the file's
|
|
15
|
+
* keyframe list by value. That round trip is lossy: a keyframe at 26.234 s in a
|
|
16
|
+
* container starting at 0.083 s comes back as 26.233999999999998, the lookup
|
|
17
|
+
* takes "the keyframe at or before" that, and answers the PREVIOUS one — 8.717 s
|
|
18
|
+
* earlier. Two parts in a quadrillion became one keyframe interval, that
|
|
19
|
+
* interval became a trim, the trim moved every cut of the run backwards by
|
|
20
|
+
* another interval, and the run's files were numbered from #36 while carrying
|
|
21
|
+
* film 17.4 s before what the playlist says #36 holds. The player's video buffer
|
|
22
|
+
* covered the playhead, its audio buffer had a 17.4 s hole across it, the
|
|
23
|
+
* intersection was empty, and the viewer waited two minutes and was told the
|
|
24
|
+
* proxy had sent no video.
|
|
25
|
+
*
|
|
26
|
+
* So the keyframe is carried, by index, from the one place that knows it.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} CutGrid
|
|
31
|
+
* @property {number[]} boundaries - Cut times on the player's clock, 0-based,
|
|
32
|
+
* ascending, one more than there are segments.
|
|
33
|
+
* @property {number[]} sourceTimes - The same cuts on the FILE's clock: for a
|
|
34
|
+
* keyframe grid, the keyframe itself, exactly as the container stated it.
|
|
35
|
+
* Same length as `boundaries`, so an index names both.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Cut a file into segments.
|
|
40
|
+
*
|
|
41
|
+
* On a keyframe grid the cuts are the container's own keyframes, thinned so
|
|
42
|
+
* that no segment is much shorter than asked for; the first cut is the start of
|
|
43
|
+
* the file and the last is its end, neither of which need be a keyframe.
|
|
44
|
+
*
|
|
45
|
+
* On a uniform grid the cuts are multiples of the segment length, and the two
|
|
46
|
+
* clocks are the same clock: a re-encode places its own keyframes and owes the
|
|
47
|
+
* container's start time nothing.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} params
|
|
50
|
+
* @param {boolean} params.useKeyframeGrid
|
|
51
|
+
* @param {number} params.durationSeconds
|
|
52
|
+
* @param {number} params.segDur - The segment length asked for.
|
|
53
|
+
* @param {number[]} [params.keyframeTimes] - On the file's own clock.
|
|
54
|
+
* @param {number} [params.startTime] - The container's start time.
|
|
55
|
+
* @returns {CutGrid}
|
|
56
|
+
*/
|
|
57
|
+
export function computeCutGrid({ useKeyframeGrid, durationSeconds, segDur, keyframeTimes, startTime }) {
|
|
58
|
+
const total = Number.isFinite(durationSeconds) && durationSeconds > 0 ? durationSeconds : 0;
|
|
59
|
+
const step = Number.isFinite(segDur) && segDur > 0 ? segDur : 4;
|
|
60
|
+
const base = Number.isFinite(startTime) ? startTime : 0;
|
|
61
|
+
const uniform = () => {
|
|
62
|
+
const boundaries = [];
|
|
63
|
+
for (let cutAt = 0; cutAt < total - 0.001; cutAt += step) {
|
|
64
|
+
boundaries.push(Number(cutAt.toFixed(6)));
|
|
65
|
+
}
|
|
66
|
+
boundaries.push(total);
|
|
67
|
+
// One clock: nothing here is a keyframe of the source, so nothing is owed
|
|
68
|
+
// the container's start time either.
|
|
69
|
+
return { boundaries, sourceTimes: [...boundaries] };
|
|
70
|
+
};
|
|
71
|
+
if (!useKeyframeGrid || !Array.isArray(keyframeTimes) || keyframeTimes.length === 0 || total <= 0) {
|
|
72
|
+
return uniform();
|
|
73
|
+
}
|
|
74
|
+
const kept = keyframeTimes
|
|
75
|
+
.filter((time) => Number.isFinite(time))
|
|
76
|
+
.map((time) => ({ source: time, published: time - base }))
|
|
77
|
+
// THE END IS MEASURED THE SAME WAY AS EVERY OTHER CUT. A keyframe nearer to
|
|
78
|
+
// the end than one segment leaves a tail too short to be a segment, and the
|
|
79
|
+
// rule below — no cut closer than a step to the one before it — never looks
|
|
80
|
+
// at the end at all. It used to be guarded by fifty milliseconds, a number
|
|
81
|
+
// from nowhere: field 2026-09-11, a keyframe 160 ms before the end of a
|
|
82
|
+
// 54-minute film passed it and left segment #541 lasting 0.16 s. The sound
|
|
83
|
+
// has no data in such a tail, so its run made 541 segments where the picture
|
|
84
|
+
// made 542, was marked short, and the repair that followed was handed a
|
|
85
|
+
// start later than its own end — 190 bytes that are not a fragment, and a
|
|
86
|
+
// viewer held 23 s at the last minute of the film for a 404.
|
|
87
|
+
.filter((cut) => cut.published >= -0.001 && cut.published < total - step)
|
|
88
|
+
.sort((left, right) => left.published - right.published);
|
|
89
|
+
// The first cut is the start of the file, whatever the container's own clock
|
|
90
|
+
// says that is; the last is its end. Neither is a keyframe, and a run never
|
|
91
|
+
// seeks to either — index 0 is served without a seek at all.
|
|
92
|
+
const boundaries = [0];
|
|
93
|
+
const sourceTimes = [base];
|
|
94
|
+
for (const cut of kept) {
|
|
95
|
+
if (cut.published >= boundaries[boundaries.length - 1] + step - 0.05) {
|
|
96
|
+
boundaries.push(Number(cut.published.toFixed(6)));
|
|
97
|
+
// NOT the rounded value with the base added back. This is the number the
|
|
98
|
+
// container stated, and it is what the seek must ask for.
|
|
99
|
+
sourceTimes.push(cut.source);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
boundaries.push(total);
|
|
103
|
+
sourceTimes.push(total + base);
|
|
104
|
+
// A degenerate index — one keyframe, or none inside the film — is no grid at
|
|
105
|
+
// all, and an even one serves better than a table with a single entry.
|
|
106
|
+
return boundaries.length >= 2 ? { boundaries, sourceTimes } : uniform();
|
|
107
|
+
}
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
export { OutputSpec, VideoOutput, AudioOutput, CutGrid, isOutputName } from "./OutputSpec.js";
|
|
2
|
-
export { PLAYLIST_FILE_NAME } from "./playlists.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
export { OutputSpec, VideoOutput, AudioOutput, CutGrid, isOutputName } from "./OutputSpec.js";
|
|
2
|
+
export { PLAYLIST_FILE_NAME } from "./playlists.js";
|
|
3
|
+
|
|
4
|
+
// FFmpeg may close a segment this far before its requested cut. Both the
|
|
5
|
+
// command and coverage validation use this existing muxer configuration.
|
|
6
|
+
export const SEGMENT_CUT_TIME_DELTA_SECONDS = 0.05;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The code an error carries when no output suits the viewer: nothing this proxy
|
|
10
|
+
* could produce or already holds is admitted by their link. Its `details` say
|
|
11
|
+
* why and with which figures. A definition shared by both sides of the
|
|
12
|
+
* interface — the encoding that decides it and the routes that answer it.
|
|
13
|
+
*/
|
|
14
|
+
export const OUTPUT_UNAVAILABLE = "OUTPUT_UNAVAILABLE";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The code an error carries when THIS MACHINE cannot take the output: no mode
|
|
18
|
+
* of its encoder was shown at startup to hold the picture at any size this
|
|
19
|
+
* viewer could be given, or the machine has no place for one more encoder
|
|
20
|
+
* beside what already runs (roadmap item 97, step 14). Not the viewer's link —
|
|
21
|
+
* another proxy may well serve it, and the page asks the pool before anything
|
|
22
|
+
* plays. Its `details` say why, with the figures.
|
|
23
|
+
*/
|
|
24
|
+
export const OUTPUT_NO_CAPACITY = "OUTPUT_NO_CAPACITY";
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file The heights a picture may be offered at.
|
|
3
|
-
*
|
|
4
|
-
* A fact about an output and about nothing else: it takes a source height and
|
|
5
|
-
* answers with heights. What the machine can afford of them is the quality
|
|
6
|
-
* budget's question, and what a player may splice between is `OutputCatalog`.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The standard heights, high to low. One list for both uses: the heights the
|
|
11
|
-
* menu offers, and the rungs the realtime budget steps down through below a
|
|
12
|
-
* viewer's own screen size. They were two lists with the same values, and two
|
|
13
|
-
* owners of one fact drift apart.
|
|
14
|
-
*
|
|
15
|
-
* Only rungs below the source are used: upscaling invents detail and costs the
|
|
16
|
-
* encoder more than the source itself.
|
|
17
|
-
*/
|
|
18
|
-
export const LADDER_HEIGHTS = Object.freeze([2160, 1440, 1080, 720, 540, 480, 360, 240]);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The heights offered for a source of this height, largest first.
|
|
22
|
-
*
|
|
23
|
-
* @param {number} sourceHeight
|
|
24
|
-
* @returns {number[]}
|
|
25
|
-
*/
|
|
26
|
-
export function variantHeightsFor(sourceHeight) {
|
|
27
|
-
if (!Number.isFinite(sourceHeight) || sourceHeight <= 0) {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
const rungs = LADDER_HEIGHTS.filter((height) => height < sourceHeight);
|
|
31
|
-
return [Math.round(sourceHeight), ...rungs];
|
|
32
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file The heights a picture may be offered at.
|
|
3
|
+
*
|
|
4
|
+
* A fact about an output and about nothing else: it takes a source height and
|
|
5
|
+
* answers with heights. What the machine can afford of them is the quality
|
|
6
|
+
* budget's question, and what a player may splice between is `OutputCatalog`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The standard heights, high to low. One list for both uses: the heights the
|
|
11
|
+
* menu offers, and the rungs the realtime budget steps down through below a
|
|
12
|
+
* viewer's own screen size. They were two lists with the same values, and two
|
|
13
|
+
* owners of one fact drift apart.
|
|
14
|
+
*
|
|
15
|
+
* Only rungs below the source are used: upscaling invents detail and costs the
|
|
16
|
+
* encoder more than the source itself.
|
|
17
|
+
*/
|
|
18
|
+
export const LADDER_HEIGHTS = Object.freeze([2160, 1440, 1080, 720, 540, 480, 360, 240]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The heights offered for a source of this height, largest first.
|
|
22
|
+
*
|
|
23
|
+
* @param {number} sourceHeight
|
|
24
|
+
* @returns {number[]}
|
|
25
|
+
*/
|
|
26
|
+
export function variantHeightsFor(sourceHeight) {
|
|
27
|
+
if (!Number.isFinite(sourceHeight) || sourceHeight <= 0) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const rungs = LADDER_HEIGHTS.filter((height) => height < sourceHeight);
|
|
31
|
+
return [Math.round(sourceHeight), ...rungs];
|
|
32
|
+
}
|