@torrent-tv/proxy 2.89.0 → 2.89.2
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 +6 -1
- package/CHANGELOG.md +15 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +42 -8
- 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/render-run-graph.js +173 -173
- 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/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +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/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/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/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-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +476 -458
- package/services/encode/segment-formats/index.js +103 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +604 -541
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1150 -1150
- package/services/server/ViewerRequests.js +790 -786
- 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 +1024 -1020
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1255 -1255
- 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/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +67 -0
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/media-time.js +355 -0
- package/services/{server → viewer}/playback-readiness.js +635 -516
- 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 -70
- 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 +146 -162
- 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/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/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/media-time.test.js +62 -0
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +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 +518 -483
- package/test/playback-start-regressions.test.js +101 -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-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 +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-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/supply-margin.test.js +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/whole-file-held-read.test.js +30 -0
- 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,62 +1,62 @@
|
|
|
1
|
-
# Proposal: Cold-start reduction (proxy side)
|
|
2
|
-
|
|
3
|
-
## Why
|
|
4
|
-
|
|
5
|
-
Field recordings (mobile tester, 2026-07-08) show ~90 seconds from picking an
|
|
6
|
-
episode to a picture. The proxy owns two chunks of that:
|
|
7
|
-
|
|
8
|
-
1. **A redundant ffmpeg input scan.** The playback planner probes the input
|
|
9
|
-
(`probeStreamCodecs`) and CACHES the plan — then `createSession` in
|
|
10
|
-
hls-session-manager immediately probes the SAME input again
|
|
11
|
-
(`probeInputMediaInfo`) for duration/resolution/fps/startTime/HDR. Each
|
|
12
|
-
probe is a full ffmpeg startup + banner scan over the torrent-backed HTTP
|
|
13
|
-
stream — seconds on the HA host, on the critical path, twice.
|
|
14
|
-
2. **First-segment piece latency.** The planner prefetches only the file
|
|
15
|
-
EDGES (256 KB head + 2 MB tail — what the codec probe needs). The first
|
|
16
|
-
segment's encode then needs the first ~10–20 MB of the file BODY, whose
|
|
17
|
-
pieces start downloading only when ffmpeg asks for them — while the swarm
|
|
18
|
-
sat mostly idle during the plan poll.
|
|
19
|
-
|
|
20
|
-
There is also no per-stage timing in the logs, so field regressions in
|
|
21
|
-
session startup are invisible.
|
|
22
|
-
|
|
23
|
-
## What Changes
|
|
24
|
-
|
|
25
|
-
- **Reuse the planner's probe in session create.** The planner parses and
|
|
26
|
-
caches the full media info (duration, width/height, fps, startTime, isHdr)
|
|
27
|
-
from the probe it already runs; hls-session-manager consults that cache
|
|
28
|
-
and skips its own `probeInputMediaInfo` on a hit (probe stays as the
|
|
29
|
-
fallback). One full ffmpeg scan disappears from the critical path.
|
|
30
|
-
- **Prefetch the file-body start once the plan is ready.** After a successful
|
|
31
|
-
probe, fire-and-forget prefetch of the first 16 MB of the file, so the
|
|
32
|
-
session's ffmpeg reads hit already-downloaded data instead of paying piece
|
|
33
|
-
latency at encode time.
|
|
34
|
-
- **Stage timings in the log.** Session create logs probe ms (and
|
|
35
|
-
cache-hit/probed), keyframe-probe ms, and the time from create to the
|
|
36
|
-
first servable segment — so cold-start is measurable per stage in the
|
|
37
|
-
field, next to the client-side summary line (server `cold-start` change).
|
|
38
|
-
|
|
39
|
-
Client-side counterparts (phase instrumentation, earlier prebuffer start)
|
|
40
|
-
live in the server repo's `cold-start` change. The two changes are
|
|
41
|
-
independent — no wire-format coupling, either releases alone.
|
|
42
|
-
|
|
43
|
-
NOT in this change (candidates for later, kept out deliberately):
|
|
44
|
-
next-episode speculative prefetch (bandwidth cost needs field data first);
|
|
45
|
-
encoder-side first-segment speed-ups (faster warm-up preset would need a
|
|
46
|
-
mid-stream switch — parked with transcode-quality 2.2b).
|
|
47
|
-
|
|
48
|
-
## Capabilities
|
|
49
|
-
|
|
50
|
-
### New Capabilities
|
|
51
|
-
|
|
52
|
-
- `cold-start`: proxy-side session startup latency behaviour.
|
|
53
|
-
|
|
54
|
-
## Impact
|
|
55
|
-
|
|
56
|
-
- `services/playback-planner.js` — parse + cache full media info; expose
|
|
57
|
-
`getCachedMediaInfo`; body-start prefetch after a successful probe.
|
|
58
|
-
- `services/hls-session-manager.js` — consult the cache in `createSession`;
|
|
59
|
-
stage-timing log lines.
|
|
60
|
-
- `server.js` — wire `getCachedMediaInfo` into the HlsSessionManager deps.
|
|
61
|
-
- Release: proxy `npm run patch` + ha-addon version bump (standard order:
|
|
62
|
-
proxy first, then addon).
|
|
1
|
+
# Proposal: Cold-start reduction (proxy side)
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
Field recordings (mobile tester, 2026-07-08) show ~90 seconds from picking an
|
|
6
|
+
episode to a picture. The proxy owns two chunks of that:
|
|
7
|
+
|
|
8
|
+
1. **A redundant ffmpeg input scan.** The playback planner probes the input
|
|
9
|
+
(`probeStreamCodecs`) and CACHES the plan — then `createSession` in
|
|
10
|
+
hls-session-manager immediately probes the SAME input again
|
|
11
|
+
(`probeInputMediaInfo`) for duration/resolution/fps/startTime/HDR. Each
|
|
12
|
+
probe is a full ffmpeg startup + banner scan over the torrent-backed HTTP
|
|
13
|
+
stream — seconds on the HA host, on the critical path, twice.
|
|
14
|
+
2. **First-segment piece latency.** The planner prefetches only the file
|
|
15
|
+
EDGES (256 KB head + 2 MB tail — what the codec probe needs). The first
|
|
16
|
+
segment's encode then needs the first ~10–20 MB of the file BODY, whose
|
|
17
|
+
pieces start downloading only when ffmpeg asks for them — while the swarm
|
|
18
|
+
sat mostly idle during the plan poll.
|
|
19
|
+
|
|
20
|
+
There is also no per-stage timing in the logs, so field regressions in
|
|
21
|
+
session startup are invisible.
|
|
22
|
+
|
|
23
|
+
## What Changes
|
|
24
|
+
|
|
25
|
+
- **Reuse the planner's probe in session create.** The planner parses and
|
|
26
|
+
caches the full media info (duration, width/height, fps, startTime, isHdr)
|
|
27
|
+
from the probe it already runs; hls-session-manager consults that cache
|
|
28
|
+
and skips its own `probeInputMediaInfo` on a hit (probe stays as the
|
|
29
|
+
fallback). One full ffmpeg scan disappears from the critical path.
|
|
30
|
+
- **Prefetch the file-body start once the plan is ready.** After a successful
|
|
31
|
+
probe, fire-and-forget prefetch of the first 16 MB of the file, so the
|
|
32
|
+
session's ffmpeg reads hit already-downloaded data instead of paying piece
|
|
33
|
+
latency at encode time.
|
|
34
|
+
- **Stage timings in the log.** Session create logs probe ms (and
|
|
35
|
+
cache-hit/probed), keyframe-probe ms, and the time from create to the
|
|
36
|
+
first servable segment — so cold-start is measurable per stage in the
|
|
37
|
+
field, next to the client-side summary line (server `cold-start` change).
|
|
38
|
+
|
|
39
|
+
Client-side counterparts (phase instrumentation, earlier prebuffer start)
|
|
40
|
+
live in the server repo's `cold-start` change. The two changes are
|
|
41
|
+
independent — no wire-format coupling, either releases alone.
|
|
42
|
+
|
|
43
|
+
NOT in this change (candidates for later, kept out deliberately):
|
|
44
|
+
next-episode speculative prefetch (bandwidth cost needs field data first);
|
|
45
|
+
encoder-side first-segment speed-ups (faster warm-up preset would need a
|
|
46
|
+
mid-stream switch — parked with transcode-quality 2.2b).
|
|
47
|
+
|
|
48
|
+
## Capabilities
|
|
49
|
+
|
|
50
|
+
### New Capabilities
|
|
51
|
+
|
|
52
|
+
- `cold-start`: proxy-side session startup latency behaviour.
|
|
53
|
+
|
|
54
|
+
## Impact
|
|
55
|
+
|
|
56
|
+
- `services/playback-planner.js` — parse + cache full media info; expose
|
|
57
|
+
`getCachedMediaInfo`; body-start prefetch after a successful probe.
|
|
58
|
+
- `services/hls-session-manager.js` — consult the cache in `createSession`;
|
|
59
|
+
stage-timing log lines.
|
|
60
|
+
- `server.js` — wire `getCachedMediaInfo` into the HlsSessionManager deps.
|
|
61
|
+
- Release: proxy `npm run patch` + ha-addon version bump (standard order:
|
|
62
|
+
proxy first, then addon).
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
# cold-start — delta spec (proxy)
|
|
2
|
-
|
|
3
|
-
## ADDED Requirements
|
|
4
|
-
|
|
5
|
-
### Requirement: Session create reuses the planner's probe
|
|
6
|
-
|
|
7
|
-
Creating an HLS transcode session SHALL NOT re-probe an input whose media
|
|
8
|
-
info the playback planner already probed and cached for the same source and
|
|
9
|
-
file. The cached info is used only when its critical fields (duration,
|
|
10
|
-
width, height) are present and valid; otherwise the session SHALL probe as
|
|
11
|
-
before. Cache lifetime and invalidation follow the plan cache exactly.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Warm plan, immediate session
|
|
14
|
-
- **WHEN** the browser requests a transcode session right after receiving a
|
|
15
|
-
playback plan for the same file
|
|
16
|
-
- **THEN** the session starts without a second ffmpeg input scan and its
|
|
17
|
-
playlist/ladder decisions are identical to what the probe would have
|
|
18
|
-
produced
|
|
19
|
-
|
|
20
|
-
#### Scenario: Cache cannot serve
|
|
21
|
-
- **WHEN** no cached media info exists (e.g. proxy restarted between plan
|
|
22
|
-
and session) or a critical field is missing
|
|
23
|
-
- **THEN** the session probes the input itself, exactly as before this
|
|
24
|
-
change
|
|
25
|
-
|
|
26
|
-
### Requirement: The file-body start is warm before the encoder needs it
|
|
27
|
-
|
|
28
|
-
Once a playback plan probe succeeds, the proxy SHALL prefetch the beginning
|
|
29
|
-
of the file body (bounded, ~16 MB) in the background, without delaying the
|
|
30
|
-
plan response and without competing with a still-running header probe.
|
|
31
|
-
|
|
32
|
-
#### Scenario: First segment does not wait for pieces
|
|
33
|
-
- **WHEN** the viewer confirms playback within the normal flow (seconds
|
|
34
|
-
after the plan)
|
|
35
|
-
- **THEN** the encoder's initial reads are served from already-downloaded
|
|
36
|
-
data and the first segment's production is not blocked on piece download
|
|
37
|
-
|
|
38
|
-
### Requirement: Session startup is measurable per stage
|
|
39
|
-
|
|
40
|
-
Session creation SHALL log the media-info acquisition time (and whether it
|
|
41
|
-
was cached or probed), the keyframe-probe time when it runs, and — once per
|
|
42
|
-
session — the time from session-create entry to the first servable segment.
|
|
43
|
-
|
|
44
|
-
#### Scenario: Field regression triage
|
|
45
|
-
- **WHEN** a tester reports a slow start
|
|
46
|
-
- **THEN** the proxy log shows, for that session, where the time went:
|
|
47
|
-
media info, keyframes, or first-segment production
|
|
1
|
+
# cold-start — delta spec (proxy)
|
|
2
|
+
|
|
3
|
+
## ADDED Requirements
|
|
4
|
+
|
|
5
|
+
### Requirement: Session create reuses the planner's probe
|
|
6
|
+
|
|
7
|
+
Creating an HLS transcode session SHALL NOT re-probe an input whose media
|
|
8
|
+
info the playback planner already probed and cached for the same source and
|
|
9
|
+
file. The cached info is used only when its critical fields (duration,
|
|
10
|
+
width, height) are present and valid; otherwise the session SHALL probe as
|
|
11
|
+
before. Cache lifetime and invalidation follow the plan cache exactly.
|
|
12
|
+
|
|
13
|
+
#### Scenario: Warm plan, immediate session
|
|
14
|
+
- **WHEN** the browser requests a transcode session right after receiving a
|
|
15
|
+
playback plan for the same file
|
|
16
|
+
- **THEN** the session starts without a second ffmpeg input scan and its
|
|
17
|
+
playlist/ladder decisions are identical to what the probe would have
|
|
18
|
+
produced
|
|
19
|
+
|
|
20
|
+
#### Scenario: Cache cannot serve
|
|
21
|
+
- **WHEN** no cached media info exists (e.g. proxy restarted between plan
|
|
22
|
+
and session) or a critical field is missing
|
|
23
|
+
- **THEN** the session probes the input itself, exactly as before this
|
|
24
|
+
change
|
|
25
|
+
|
|
26
|
+
### Requirement: The file-body start is warm before the encoder needs it
|
|
27
|
+
|
|
28
|
+
Once a playback plan probe succeeds, the proxy SHALL prefetch the beginning
|
|
29
|
+
of the file body (bounded, ~16 MB) in the background, without delaying the
|
|
30
|
+
plan response and without competing with a still-running header probe.
|
|
31
|
+
|
|
32
|
+
#### Scenario: First segment does not wait for pieces
|
|
33
|
+
- **WHEN** the viewer confirms playback within the normal flow (seconds
|
|
34
|
+
after the plan)
|
|
35
|
+
- **THEN** the encoder's initial reads are served from already-downloaded
|
|
36
|
+
data and the first segment's production is not blocked on piece download
|
|
37
|
+
|
|
38
|
+
### Requirement: Session startup is measurable per stage
|
|
39
|
+
|
|
40
|
+
Session creation SHALL log the media-info acquisition time (and whether it
|
|
41
|
+
was cached or probed), the keyframe-probe time when it runs, and — once per
|
|
42
|
+
session — the time from session-create entry to the first servable segment.
|
|
43
|
+
|
|
44
|
+
#### Scenario: Field regression triage
|
|
45
|
+
- **WHEN** a tester reports a slow start
|
|
46
|
+
- **THEN** the proxy log shows, for that session, where the time went:
|
|
47
|
+
media info, keyframes, or first-segment production
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
# Tasks: Cold-start reduction (proxy side)
|
|
2
|
-
|
|
3
|
-
Execute in order; design.md is normative. Read the code regions listed at
|
|
4
|
-
the top of design.md first.
|
|
5
|
-
|
|
6
|
-
## 1. Media-info reuse
|
|
7
|
-
|
|
8
|
-
- [ ] 1.1 Make the banner-parse helpers of `probeInputMediaInfo` reusable by
|
|
9
|
-
the planner (export or shared module — no duplication). `node --check`
|
|
10
|
-
both files.
|
|
11
|
-
- [ ] 1.2 playback-planner: build + cache `mediaInfo` alongside the plan on
|
|
12
|
-
a codecs-detected probe; expose `getCachedMediaInfo({ sourceKey,
|
|
13
|
-
fileIndex })`. Pending probes cache nothing.
|
|
14
|
-
- [ ] 1.3 server.js: pass `getCachedMediaInfo` into HlsSessionManager;
|
|
15
|
-
hls-session-manager: consult it in `createSession` with the
|
|
16
|
-
critical-fields gate (duration/width/height), probe as fallback.
|
|
17
|
-
- [ ] 1.4 Verify equivalence: for one file, run plan → session with the
|
|
18
|
-
cache and (by temporarily disabling the wiring) without it — the
|
|
19
|
-
session log line (duration, segments, ladder/rung) must be identical.
|
|
20
|
-
|
|
21
|
-
## 2. Body-start prefetch
|
|
22
|
-
|
|
23
|
-
- [ ] 2.1 playback-planner: fire-and-forget 16 MB head prefetch
|
|
24
|
-
(`prefetchFileEdges` with `tailBytes: 0`) at the probe-success point;
|
|
25
|
-
never on pending polls; never awaited.
|
|
26
|
-
- [ ] 2.2 Verify: on a fresh well-seeded magnet, proxy log/order shows the
|
|
27
|
-
prefetch starting right after the plan while the session that follows
|
|
28
|
-
produces its first segment without a piece-wait stall (compare
|
|
29
|
-
first-segment ms with task 3 timings before/after).
|
|
30
|
-
|
|
31
|
-
## 3. Stage timings
|
|
32
|
-
|
|
33
|
-
- [ ] 3.1 `createSession`: one `cold-start …: media-info=<ms> (cached|probed)
|
|
34
|
-
keyframes=<ms|skipped> create-total=<ms>` line.
|
|
35
|
-
- [ ] 3.2 First-segment-ready line (`+<ms>` since create entry), once per
|
|
36
|
-
session (timestamp on the session object + boolean guard).
|
|
37
|
-
|
|
38
|
-
## 4. Verification and release
|
|
39
|
-
|
|
40
|
-
- [ ] 4.1 Regression: normal HLS transcode start, video-copy start (keyframe
|
|
41
|
-
probe still runs), seek-restart, quality switch — behaviour unchanged;
|
|
42
|
-
timings visible in the log.
|
|
43
|
-
- [ ] 4.2 Before/after numbers on the dev HA proxy for one cold magnet:
|
|
44
|
-
media-info ms (probed → cached) and first-segment ms. Record them in
|
|
45
|
-
this file.
|
|
46
|
-
- [ ] 4.3 CHANGELOG entry (current version + 1 patch); `npm run patch`; bump
|
|
47
|
-
ha-addon `config.yaml` + its CHANGELOG; push; update the addon in HA
|
|
48
|
-
(proxy FIRST, then addon — release order per root CLAUDE.md).
|
|
1
|
+
# Tasks: Cold-start reduction (proxy side)
|
|
2
|
+
|
|
3
|
+
Execute in order; design.md is normative. Read the code regions listed at
|
|
4
|
+
the top of design.md first.
|
|
5
|
+
|
|
6
|
+
## 1. Media-info reuse
|
|
7
|
+
|
|
8
|
+
- [ ] 1.1 Make the banner-parse helpers of `probeInputMediaInfo` reusable by
|
|
9
|
+
the planner (export or shared module — no duplication). `node --check`
|
|
10
|
+
both files.
|
|
11
|
+
- [ ] 1.2 playback-planner: build + cache `mediaInfo` alongside the plan on
|
|
12
|
+
a codecs-detected probe; expose `getCachedMediaInfo({ sourceKey,
|
|
13
|
+
fileIndex })`. Pending probes cache nothing.
|
|
14
|
+
- [ ] 1.3 server.js: pass `getCachedMediaInfo` into HlsSessionManager;
|
|
15
|
+
hls-session-manager: consult it in `createSession` with the
|
|
16
|
+
critical-fields gate (duration/width/height), probe as fallback.
|
|
17
|
+
- [ ] 1.4 Verify equivalence: for one file, run plan → session with the
|
|
18
|
+
cache and (by temporarily disabling the wiring) without it — the
|
|
19
|
+
session log line (duration, segments, ladder/rung) must be identical.
|
|
20
|
+
|
|
21
|
+
## 2. Body-start prefetch
|
|
22
|
+
|
|
23
|
+
- [ ] 2.1 playback-planner: fire-and-forget 16 MB head prefetch
|
|
24
|
+
(`prefetchFileEdges` with `tailBytes: 0`) at the probe-success point;
|
|
25
|
+
never on pending polls; never awaited.
|
|
26
|
+
- [ ] 2.2 Verify: on a fresh well-seeded magnet, proxy log/order shows the
|
|
27
|
+
prefetch starting right after the plan while the session that follows
|
|
28
|
+
produces its first segment without a piece-wait stall (compare
|
|
29
|
+
first-segment ms with task 3 timings before/after).
|
|
30
|
+
|
|
31
|
+
## 3. Stage timings
|
|
32
|
+
|
|
33
|
+
- [ ] 3.1 `createSession`: one `cold-start …: media-info=<ms> (cached|probed)
|
|
34
|
+
keyframes=<ms|skipped> create-total=<ms>` line.
|
|
35
|
+
- [ ] 3.2 First-segment-ready line (`+<ms>` since create entry), once per
|
|
36
|
+
session (timestamp on the session object + boolean guard).
|
|
37
|
+
|
|
38
|
+
## 4. Verification and release
|
|
39
|
+
|
|
40
|
+
- [ ] 4.1 Regression: normal HLS transcode start, video-copy start (keyframe
|
|
41
|
+
probe still runs), seek-restart, quality switch — behaviour unchanged;
|
|
42
|
+
timings visible in the log.
|
|
43
|
+
- [ ] 4.2 Before/after numbers on the dev HA proxy for one cold magnet:
|
|
44
|
+
media-info ms (probed → cached) and first-segment ms. Record them in
|
|
45
|
+
this file.
|
|
46
|
+
- [ ] 4.3 CHANGELOG entry (current version + 1 patch); `npm run patch`; bump
|
|
47
|
+
ha-addon `config.yaml` + its CHANGELOG; push; update the addon in HA
|
|
48
|
+
(proxy FIRST, then addon — release order per root CLAUDE.md).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
schema: spec-driven
|
|
2
|
-
created: 2026-07-07
|
|
1
|
+
schema: spec-driven
|
|
2
|
+
created: 2026-07-07
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# Proposal: Global disk cap with LRU eviction (Disk hygiene Level 1, final)
|
|
2
|
-
|
|
3
|
-
## Why
|
|
4
|
-
|
|
5
|
-
Downloaded torrent data is already removed on a 300 s idle TTL and at
|
|
6
|
-
shutdown, and orphans are swept at startup — but under pressure (several
|
|
7
|
-
large files opened within the TTL window, or a fast fill) the total can
|
|
8
|
-
still grow unbounded and fill a small Home Assistant host's disk
|
|
9
|
-
(SD/eMMC on a Yellow/Pi is often 16–32 GB). A full disk can take down Home
|
|
10
|
-
Assistant itself. This adds the last missing Level 1 piece: a global cap.
|
|
11
|
-
|
|
12
|
-
## What Changes
|
|
13
|
-
|
|
14
|
-
- The pool tracks total downloaded bytes and, when it exceeds a cap, evicts
|
|
15
|
-
whole torrents with NO active reader, least-recently-used first, until
|
|
16
|
-
back under the cap (checked every 30 s and reused via the existing
|
|
17
|
-
remove-with-store path). A torrent that is currently playing is never
|
|
18
|
-
evicted — we cannot delete what is in use.
|
|
19
|
-
- The cap defaults to `min(10 GB, half of free disk)` (measured via
|
|
20
|
-
`statfs` on the store filesystem), and is overridable with
|
|
21
|
-
`--max-disk-bytes` (0 disables).
|
|
22
|
-
|
|
23
|
-
## Capabilities
|
|
24
|
-
|
|
25
|
-
### New Capabilities
|
|
26
|
-
|
|
27
|
-
- `disk-cap`: bounded total on-disk footprint via LRU eviction.
|
|
28
|
-
|
|
29
|
-
### Modified Capabilities
|
|
30
|
-
|
|
31
|
-
<!-- none -->
|
|
32
|
-
|
|
33
|
-
## Impact
|
|
34
|
-
|
|
35
|
-
- `services/torrent/torrent-pool.js` (access tracking, cap enforcement),
|
|
36
|
-
`bin/cli.js` (`--max-disk-bytes`), `server.js` (option pass-through);
|
|
37
|
-
ha-addon bump. Part of the proxy transcode/hygiene batch (2.9.29).
|
|
1
|
+
# Proposal: Global disk cap with LRU eviction (Disk hygiene Level 1, final)
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
Downloaded torrent data is already removed on a 300 s idle TTL and at
|
|
6
|
+
shutdown, and orphans are swept at startup — but under pressure (several
|
|
7
|
+
large files opened within the TTL window, or a fast fill) the total can
|
|
8
|
+
still grow unbounded and fill a small Home Assistant host's disk
|
|
9
|
+
(SD/eMMC on a Yellow/Pi is often 16–32 GB). A full disk can take down Home
|
|
10
|
+
Assistant itself. This adds the last missing Level 1 piece: a global cap.
|
|
11
|
+
|
|
12
|
+
## What Changes
|
|
13
|
+
|
|
14
|
+
- The pool tracks total downloaded bytes and, when it exceeds a cap, evicts
|
|
15
|
+
whole torrents with NO active reader, least-recently-used first, until
|
|
16
|
+
back under the cap (checked every 30 s and reused via the existing
|
|
17
|
+
remove-with-store path). A torrent that is currently playing is never
|
|
18
|
+
evicted — we cannot delete what is in use.
|
|
19
|
+
- The cap defaults to `min(10 GB, half of free disk)` (measured via
|
|
20
|
+
`statfs` on the store filesystem), and is overridable with
|
|
21
|
+
`--max-disk-bytes` (0 disables).
|
|
22
|
+
|
|
23
|
+
## Capabilities
|
|
24
|
+
|
|
25
|
+
### New Capabilities
|
|
26
|
+
|
|
27
|
+
- `disk-cap`: bounded total on-disk footprint via LRU eviction.
|
|
28
|
+
|
|
29
|
+
### Modified Capabilities
|
|
30
|
+
|
|
31
|
+
<!-- none -->
|
|
32
|
+
|
|
33
|
+
## Impact
|
|
34
|
+
|
|
35
|
+
- `services/torrent/torrent-pool.js` (access tracking, cap enforcement),
|
|
36
|
+
`bin/cli.js` (`--max-disk-bytes`), `server.js` (option pass-through);
|
|
37
|
+
ha-addon bump. Part of the proxy transcode/hygiene batch (2.9.29).
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
# disk-cap — delta spec
|
|
2
|
-
|
|
3
|
-
## ADDED Requirements
|
|
4
|
-
|
|
5
|
-
### Requirement: Total torrent data is bounded by a disk cap
|
|
6
|
-
The pool SHALL keep the total downloaded torrent footprint under a cap. When
|
|
7
|
-
the total exceeds the cap it SHALL evict whole torrents that have no active
|
|
8
|
-
file reader, least-recently-used first, removing each with its on-disk store,
|
|
9
|
-
until back under the cap or no evictable torrent remains. A torrent with an
|
|
10
|
-
active reader SHALL NEVER be evicted. The cap SHALL default to the smaller of
|
|
11
|
-
10 GB and half the free disk, and be overridable (0 disables).
|
|
12
|
-
|
|
13
|
-
#### Scenario: Idle torrents evicted under pressure
|
|
14
|
-
- **WHEN** the total downloaded data exceeds the cap and some torrents have no
|
|
15
|
-
active reader
|
|
16
|
-
- **THEN** the least-recently-used idle torrents are removed with their stores
|
|
17
|
-
until the total is back under the cap
|
|
18
|
-
|
|
19
|
-
#### Scenario: Active torrent protected
|
|
20
|
-
- **WHEN** the cap is exceeded but the only large torrent is currently playing
|
|
21
|
-
- **THEN** it is not evicted (the cap cannot delete in-use data)
|
|
22
|
-
|
|
23
|
-
#### Scenario: Cap disabled
|
|
24
|
-
- **WHEN** the cap is set to 0
|
|
25
|
-
- **THEN** no eviction occurs
|
|
1
|
+
# disk-cap — delta spec
|
|
2
|
+
|
|
3
|
+
## ADDED Requirements
|
|
4
|
+
|
|
5
|
+
### Requirement: Total torrent data is bounded by a disk cap
|
|
6
|
+
The pool SHALL keep the total downloaded torrent footprint under a cap. When
|
|
7
|
+
the total exceeds the cap it SHALL evict whole torrents that have no active
|
|
8
|
+
file reader, least-recently-used first, removing each with its on-disk store,
|
|
9
|
+
until back under the cap or no evictable torrent remains. A torrent with an
|
|
10
|
+
active reader SHALL NEVER be evicted. The cap SHALL default to the smaller of
|
|
11
|
+
10 GB and half the free disk, and be overridable (0 disables).
|
|
12
|
+
|
|
13
|
+
#### Scenario: Idle torrents evicted under pressure
|
|
14
|
+
- **WHEN** the total downloaded data exceeds the cap and some torrents have no
|
|
15
|
+
active reader
|
|
16
|
+
- **THEN** the least-recently-used idle torrents are removed with their stores
|
|
17
|
+
until the total is back under the cap
|
|
18
|
+
|
|
19
|
+
#### Scenario: Active torrent protected
|
|
20
|
+
- **WHEN** the cap is exceeded but the only large torrent is currently playing
|
|
21
|
+
- **THEN** it is not evicted (the cap cannot delete in-use data)
|
|
22
|
+
|
|
23
|
+
#### Scenario: Cap disabled
|
|
24
|
+
- **WHEN** the cap is set to 0
|
|
25
|
+
- **THEN** no eviction occurs
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# Tasks: Global disk cap with LRU eviction
|
|
2
|
-
|
|
3
|
-
## 1. Implementation
|
|
4
|
-
|
|
5
|
-
- [x] 1.1 Access tracking (`#lastAccess`) updated on getTorrent (incl. the
|
|
6
|
-
duplicate-infoHash path) and acquireFile
|
|
7
|
-
- [x] 1.2 Cap computed at construction (`min(10GB, half free)` via statfs) or
|
|
8
|
-
taken from the `maxDiskBytes` option; 0 disables
|
|
9
|
-
- [x] 1.3 Periodic (30 s) `#enforceDiskCap`: evict zero-reader torrents
|
|
10
|
-
LRU-first via the existing remove-with-store path; clear timer on
|
|
11
|
-
destroyAll; drop `#lastAccess` on removal
|
|
12
|
-
- [x] 1.4 `--max-disk-bytes` CLI flag → server.js → pool
|
|
13
|
-
- [x] 1.5 Syntax checks + unit-test the eviction ordering/active-skip
|
|
14
|
-
|
|
15
|
-
## 2. Release
|
|
16
|
-
|
|
17
|
-
- [ ] 2.1 Ship in the proxy batch (2.9.29) + ha-addon bump
|
|
18
|
-
- [ ] 2.2 Field-check on the host: open several large files, confirm idle
|
|
19
|
-
ones are evicted and playback is never interrupted
|
|
1
|
+
# Tasks: Global disk cap with LRU eviction
|
|
2
|
+
|
|
3
|
+
## 1. Implementation
|
|
4
|
+
|
|
5
|
+
- [x] 1.1 Access tracking (`#lastAccess`) updated on getTorrent (incl. the
|
|
6
|
+
duplicate-infoHash path) and acquireFile
|
|
7
|
+
- [x] 1.2 Cap computed at construction (`min(10GB, half free)` via statfs) or
|
|
8
|
+
taken from the `maxDiskBytes` option; 0 disables
|
|
9
|
+
- [x] 1.3 Periodic (30 s) `#enforceDiskCap`: evict zero-reader torrents
|
|
10
|
+
LRU-first via the existing remove-with-store path; clear timer on
|
|
11
|
+
destroyAll; drop `#lastAccess` on removal
|
|
12
|
+
- [x] 1.4 `--max-disk-bytes` CLI flag → server.js → pool
|
|
13
|
+
- [x] 1.5 Syntax checks + unit-test the eviction ordering/active-skip
|
|
14
|
+
|
|
15
|
+
## 2. Release
|
|
16
|
+
|
|
17
|
+
- [ ] 2.1 Ship in the proxy batch (2.9.29) + ha-addon bump
|
|
18
|
+
- [ ] 2.2 Field-check on the host: open several large files, confirm idle
|
|
19
|
+
ones are evicted and playback is never interrupted
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
schema: spec-driven
|
|
2
|
-
created: 2026-07-06
|
|
1
|
+
schema: spec-driven
|
|
2
|
+
created: 2026-07-06
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
# Design: Proxy observability
|
|
2
|
-
|
|
3
|
-
## Context
|
|
4
|
-
|
|
5
|
-
Diagnosing the 2026-07-06 failures required docker exec into the addon
|
|
6
|
-
container (version) and offered no explanation for zero-peer torrents.
|
|
7
|
-
See proposal for the three gaps.
|
|
8
|
-
|
|
9
|
-
## Goals / Non-Goals
|
|
10
|
-
|
|
11
|
-
**Goals:** remote version visibility; explainable zero-peer torrents; clean
|
|
12
|
-
logs.
|
|
13
|
-
|
|
14
|
-
**Non-Goals:** peer-origin tags in `[stats]` (tracker/DHT/PEX attribution is
|
|
15
|
-
not cheaply available on WebTorrent's public API — dropped from the original
|
|
16
|
-
proposal scope); structured/queryable log output.
|
|
17
|
-
|
|
18
|
-
## Decisions
|
|
19
|
-
|
|
20
|
-
1. **Version via createRequire of package.json** (same pattern as the server
|
|
21
|
-
repo), passed as a route dep — no version literal to forget on release.
|
|
22
|
-
2. **Announce results from `torrent.discovery.tracker` ("update" event)** —
|
|
23
|
-
internal WebTorrent API, therefore guarded with optional chaining and a
|
|
24
|
-
fallback log line when absent; a breaking WebTorrent upgrade degrades to
|
|
25
|
-
"not logged", never to a crash. Torrent-level `warning` events carry
|
|
26
|
-
tracker rejections/errors and are a public API.
|
|
27
|
-
3. **SSDP fix = setMaxListeners(0) on the one emitter** right after the
|
|
28
|
-
first successful map() (the UPnP client is created lazily by it).
|
|
29
|
-
Alternatives rejected: raising EventEmitter.defaultMaxListeners (global,
|
|
30
|
-
hides real leaks elsewhere); one shared NatAPI for both mappers (couples
|
|
31
|
-
TCP/UDP lifecycles for no gain).
|
|
32
|
-
|
|
33
|
-
## Risks / Trade-offs
|
|
34
|
-
|
|
35
|
-
- [Announce logging depends on WebTorrent internals] → defensive access +
|
|
36
|
-
explicit fallback line; covered by the spec's best-effort clause.
|
|
37
|
-
- [Announce lines add log volume] → one line per announce interval
|
|
38
|
-
(minutes), negligible next to the existing [stats] cadence.
|
|
1
|
+
# Design: Proxy observability
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Diagnosing the 2026-07-06 failures required docker exec into the addon
|
|
6
|
+
container (version) and offered no explanation for zero-peer torrents.
|
|
7
|
+
See proposal for the three gaps.
|
|
8
|
+
|
|
9
|
+
## Goals / Non-Goals
|
|
10
|
+
|
|
11
|
+
**Goals:** remote version visibility; explainable zero-peer torrents; clean
|
|
12
|
+
logs.
|
|
13
|
+
|
|
14
|
+
**Non-Goals:** peer-origin tags in `[stats]` (tracker/DHT/PEX attribution is
|
|
15
|
+
not cheaply available on WebTorrent's public API — dropped from the original
|
|
16
|
+
proposal scope); structured/queryable log output.
|
|
17
|
+
|
|
18
|
+
## Decisions
|
|
19
|
+
|
|
20
|
+
1. **Version via createRequire of package.json** (same pattern as the server
|
|
21
|
+
repo), passed as a route dep — no version literal to forget on release.
|
|
22
|
+
2. **Announce results from `torrent.discovery.tracker` ("update" event)** —
|
|
23
|
+
internal WebTorrent API, therefore guarded with optional chaining and a
|
|
24
|
+
fallback log line when absent; a breaking WebTorrent upgrade degrades to
|
|
25
|
+
"not logged", never to a crash. Torrent-level `warning` events carry
|
|
26
|
+
tracker rejections/errors and are a public API.
|
|
27
|
+
3. **SSDP fix = setMaxListeners(0) on the one emitter** right after the
|
|
28
|
+
first successful map() (the UPnP client is created lazily by it).
|
|
29
|
+
Alternatives rejected: raising EventEmitter.defaultMaxListeners (global,
|
|
30
|
+
hides real leaks elsewhere); one shared NatAPI for both mappers (couples
|
|
31
|
+
TCP/UDP lifecycles for no gain).
|
|
32
|
+
|
|
33
|
+
## Risks / Trade-offs
|
|
34
|
+
|
|
35
|
+
- [Announce logging depends on WebTorrent internals] → defensive access +
|
|
36
|
+
explicit fallback line; covered by the spec's best-effort clause.
|
|
37
|
+
- [Announce lines add log volume] → one line per announce interval
|
|
38
|
+
(minutes), negligible next to the existing [stats] cadence.
|