@torrent-tv/proxy 2.87.20 → 2.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +8 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +133 -133
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +50 -50
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +106 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +138 -138
- package/routes/api/transcode-sessions/post.js +173 -173
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +380 -380
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/server.js +725 -725
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +813 -813
- package/services/encode/EncodeRuns.js +1235 -1235
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +758 -758
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2184 -2184
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1141 -1141
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +315 -315
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +754 -754
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +456 -456
- package/services/encode/segment-formats/index.js +85 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +515 -515
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +245 -245
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1150 -1150
- package/services/server/ViewerRequests.js +760 -760
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/playback-readiness.js +696 -696
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +992 -992
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1931 -1908
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1240 -1240
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +382 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/episode-naming.js +182 -0
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-pool.js +2675 -2675
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -765
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/torrent/worker/worker.js +1129 -1129
- package/services/transport/data-channel-handler.js +1369 -1369
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +162 -162
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/episode-naming.test.js +131 -0
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-presentation-clock.test.js +147 -147
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +401 -401
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -365
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +453 -453
- package/test/playback-start-regressions.test.js +99 -99
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +12 -2
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +209 -209
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Container orchestrator — application layer over Container domain.
|
|
3
|
-
*
|
|
4
|
-
* Holds a per-file cache of Container instances (key sourceKey:fileIndex) so
|
|
5
|
-
* Tracks and keyframe index are read once per file, not per request.
|
|
6
|
-
* Delegates format detection to ContainerFactory. Transport-agnostic — takes
|
|
7
|
-
* readRange, knows nothing about torrents or HTTP.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { ContainerFactory } from "./container/ContainerFactory.js";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* How much of a file's head its track table lives in.
|
|
14
|
-
*
|
|
15
|
-
* A fact of the FORMATS, not of the torrent: Matroska puts its Tracks element
|
|
16
|
-
* in the head and MP4's `moov` is there or pointed to from there. Whoever
|
|
17
|
-
* fetches bytes before asking is told this figure rather than choosing one.
|
|
18
|
-
*/
|
|
19
|
-
export const CONTAINER_HEAD_BYTES = 256 * 1024;
|
|
20
|
-
import { logger } from "../../utils/logger.js";
|
|
21
|
-
|
|
22
|
-
export class ContainerOrchestrator {
|
|
23
|
-
constructor() {
|
|
24
|
-
/** @type {Map<string, import("./container/Container.js").Container|null>} */
|
|
25
|
-
this.cache = new Map();
|
|
26
|
-
/** @type {Map<string, Promise<import("./container/Container.js").Container|null>>} */
|
|
27
|
-
this.pending = new Map();
|
|
28
|
-
/**
|
|
29
|
-
* What each file's container declares, once it has said anything.
|
|
30
|
-
*
|
|
31
|
-
* The container parses on every ask, so without this the header is read
|
|
32
|
-
* again for each question — and the track table is asked for the audio
|
|
33
|
-
* menu, the subtitle defaults and the video facts of one file. It used to
|
|
34
|
-
* be kept in the torrent thread (`torrent-worker/container-tracks.js`), which is where the
|
|
35
|
-
* parse used to happen; it is the same one cache in its new place, not a
|
|
36
|
-
* second one.
|
|
37
|
-
*
|
|
38
|
-
* @type {Map<string, import("./tracks/index.js").ContainerTrack[]>}
|
|
39
|
-
*/
|
|
40
|
-
this.tracks = new Map();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @param {object} params
|
|
45
|
-
* @param {string} params.sourceKey
|
|
46
|
-
* @param {number} params.fileIndex
|
|
47
|
-
* @param {(start:number,end:number)=>Promise<Buffer|null>} params.readRange
|
|
48
|
-
* @param {number} params.fileSize
|
|
49
|
-
* @param {string} [params.label]
|
|
50
|
-
* @returns {Promise<import("./container/Container.js").Container|null>}
|
|
51
|
-
*/
|
|
52
|
-
async getContainer({ sourceKey, fileIndex, readRange, fileSize, label = "" }) {
|
|
53
|
-
const key = `${sourceKey}:${fileIndex}`;
|
|
54
|
-
if (this.cache.has(key)) return this.cache.get(key);
|
|
55
|
-
if (this.pending.has(key)) return this.pending.get(key);
|
|
56
|
-
const p = ContainerFactory.create({ readRange, fileSize, label }).then((c) => {
|
|
57
|
-
this.cache.set(key, c);
|
|
58
|
-
this.pending.delete(key);
|
|
59
|
-
if (c) logger.info(`container: ${c.formatName} for "${label}"`);
|
|
60
|
-
else logger.info(`container: unknown for "${label}"`);
|
|
61
|
-
return c;
|
|
62
|
-
}).catch((e) => {
|
|
63
|
-
this.pending.delete(key);
|
|
64
|
-
logger.warn(`container: failed for "${label}": ${e?.message ?? e}`);
|
|
65
|
-
return null;
|
|
66
|
-
});
|
|
67
|
-
this.pending.set(key, p);
|
|
68
|
-
return p;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* @param {object} params - same as getContainer
|
|
73
|
-
* @returns {Promise<import("./tracks/index.js").ContainerTrack[]>}
|
|
74
|
-
*/
|
|
75
|
-
async getTracks(params) {
|
|
76
|
-
const key = `${params.sourceKey}:${params.fileIndex}`;
|
|
77
|
-
const known = this.tracks.get(key);
|
|
78
|
-
if (known) {
|
|
79
|
-
return known;
|
|
80
|
-
}
|
|
81
|
-
const container = await this.getContainer(params);
|
|
82
|
-
if (!container) return [];
|
|
83
|
-
try {
|
|
84
|
-
const tracks = await container.readTracks();
|
|
85
|
-
// ONLY A NON-EMPTY READING IS KEPT. An empty one usually means the header
|
|
86
|
-
// has not arrived off the swarm yet, and caching that would answer "this
|
|
87
|
-
// file declares nothing" for the life of the process — which is what
|
|
88
|
-
// decides whether the viewer is offered a soundtrack at all.
|
|
89
|
-
if (Array.isArray(tracks) && tracks.length > 0) {
|
|
90
|
-
this.tracks.set(key, tracks);
|
|
91
|
-
return tracks;
|
|
92
|
-
}
|
|
93
|
-
return Array.isArray(tracks) ? tracks : [];
|
|
94
|
-
} catch (e) {
|
|
95
|
-
logger.warn(`container: readTracks failed for "${params.label}": ${e?.message ?? e}`);
|
|
96
|
-
return [];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* What the file declares about itself as a whole — format, duration, and
|
|
102
|
-
* where its own timeline begins.
|
|
103
|
-
*
|
|
104
|
-
* Read once per file, like the track table beside it, and from the same 64 KB
|
|
105
|
-
* of header. A `null` field means the container does not declare it, which is
|
|
106
|
-
* a final answer about the container.
|
|
107
|
-
*
|
|
108
|
-
* @param {object} params - same as getContainer
|
|
109
|
-
* @returns {Promise<import("./container/Container.js").ContainerMediaInfo|null>}
|
|
110
|
-
*/
|
|
111
|
-
async getMediaInfo(params) {
|
|
112
|
-
const container = await this.getContainer(params);
|
|
113
|
-
if (!container) return null;
|
|
114
|
-
try {
|
|
115
|
-
return await container.readMediaInfo();
|
|
116
|
-
} catch (e) {
|
|
117
|
-
logger.warn(`container: readMediaInfo failed for "${params.label}": ${e?.message ?? e}`);
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* @param {object} params - same as getContainer
|
|
124
|
-
* @returns {Promise<{times:number[],tolerance:number}|null>}
|
|
125
|
-
*/
|
|
126
|
-
async getKeyframeIndex(params) {
|
|
127
|
-
const container = await this.getContainer(params);
|
|
128
|
-
if (!container) return null;
|
|
129
|
-
try {
|
|
130
|
-
return await container.readKeyframeIndex();
|
|
131
|
-
} catch {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
forget(sourceKey, fileIndex) {
|
|
137
|
-
if (fileIndex === undefined) {
|
|
138
|
-
for (const k of [...this.cache.keys()]) if (k.startsWith(`${sourceKey}:`)) this.cache.delete(k);
|
|
139
|
-
for (const k of [...this.pending.keys()]) if (k.startsWith(`${sourceKey}:`)) this.pending.delete(k);
|
|
140
|
-
for (const k of [...this.tracks.keys()]) if (k.startsWith(`${sourceKey}:`)) this.tracks.delete(k);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
this.cache.delete(`${sourceKey}:${fileIndex}`);
|
|
144
|
-
this.pending.delete(`${sourceKey}:${fileIndex}`);
|
|
145
|
-
this.tracks.delete(`${sourceKey}:${fileIndex}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export const containerOrchestrator = new ContainerOrchestrator();
|
|
1
|
+
/**
|
|
2
|
+
* @file Container orchestrator — application layer over Container domain.
|
|
3
|
+
*
|
|
4
|
+
* Holds a per-file cache of Container instances (key sourceKey:fileIndex) so
|
|
5
|
+
* Tracks and keyframe index are read once per file, not per request.
|
|
6
|
+
* Delegates format detection to ContainerFactory. Transport-agnostic — takes
|
|
7
|
+
* readRange, knows nothing about torrents or HTTP.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ContainerFactory } from "./container/ContainerFactory.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How much of a file's head its track table lives in.
|
|
14
|
+
*
|
|
15
|
+
* A fact of the FORMATS, not of the torrent: Matroska puts its Tracks element
|
|
16
|
+
* in the head and MP4's `moov` is there or pointed to from there. Whoever
|
|
17
|
+
* fetches bytes before asking is told this figure rather than choosing one.
|
|
18
|
+
*/
|
|
19
|
+
export const CONTAINER_HEAD_BYTES = 256 * 1024;
|
|
20
|
+
import { logger } from "../../utils/logger.js";
|
|
21
|
+
|
|
22
|
+
export class ContainerOrchestrator {
|
|
23
|
+
constructor() {
|
|
24
|
+
/** @type {Map<string, import("./container/Container.js").Container|null>} */
|
|
25
|
+
this.cache = new Map();
|
|
26
|
+
/** @type {Map<string, Promise<import("./container/Container.js").Container|null>>} */
|
|
27
|
+
this.pending = new Map();
|
|
28
|
+
/**
|
|
29
|
+
* What each file's container declares, once it has said anything.
|
|
30
|
+
*
|
|
31
|
+
* The container parses on every ask, so without this the header is read
|
|
32
|
+
* again for each question — and the track table is asked for the audio
|
|
33
|
+
* menu, the subtitle defaults and the video facts of one file. It used to
|
|
34
|
+
* be kept in the torrent thread (`torrent-worker/container-tracks.js`), which is where the
|
|
35
|
+
* parse used to happen; it is the same one cache in its new place, not a
|
|
36
|
+
* second one.
|
|
37
|
+
*
|
|
38
|
+
* @type {Map<string, import("./tracks/index.js").ContainerTrack[]>}
|
|
39
|
+
*/
|
|
40
|
+
this.tracks = new Map();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} params
|
|
45
|
+
* @param {string} params.sourceKey
|
|
46
|
+
* @param {number} params.fileIndex
|
|
47
|
+
* @param {(start:number,end:number)=>Promise<Buffer|null>} params.readRange
|
|
48
|
+
* @param {number} params.fileSize
|
|
49
|
+
* @param {string} [params.label]
|
|
50
|
+
* @returns {Promise<import("./container/Container.js").Container|null>}
|
|
51
|
+
*/
|
|
52
|
+
async getContainer({ sourceKey, fileIndex, readRange, fileSize, label = "" }) {
|
|
53
|
+
const key = `${sourceKey}:${fileIndex}`;
|
|
54
|
+
if (this.cache.has(key)) return this.cache.get(key);
|
|
55
|
+
if (this.pending.has(key)) return this.pending.get(key);
|
|
56
|
+
const p = ContainerFactory.create({ readRange, fileSize, label }).then((c) => {
|
|
57
|
+
this.cache.set(key, c);
|
|
58
|
+
this.pending.delete(key);
|
|
59
|
+
if (c) logger.info(`container: ${c.formatName} for "${label}"`);
|
|
60
|
+
else logger.info(`container: unknown for "${label}"`);
|
|
61
|
+
return c;
|
|
62
|
+
}).catch((e) => {
|
|
63
|
+
this.pending.delete(key);
|
|
64
|
+
logger.warn(`container: failed for "${label}": ${e?.message ?? e}`);
|
|
65
|
+
return null;
|
|
66
|
+
});
|
|
67
|
+
this.pending.set(key, p);
|
|
68
|
+
return p;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {object} params - same as getContainer
|
|
73
|
+
* @returns {Promise<import("./tracks/index.js").ContainerTrack[]>}
|
|
74
|
+
*/
|
|
75
|
+
async getTracks(params) {
|
|
76
|
+
const key = `${params.sourceKey}:${params.fileIndex}`;
|
|
77
|
+
const known = this.tracks.get(key);
|
|
78
|
+
if (known) {
|
|
79
|
+
return known;
|
|
80
|
+
}
|
|
81
|
+
const container = await this.getContainer(params);
|
|
82
|
+
if (!container) return [];
|
|
83
|
+
try {
|
|
84
|
+
const tracks = await container.readTracks();
|
|
85
|
+
// ONLY A NON-EMPTY READING IS KEPT. An empty one usually means the header
|
|
86
|
+
// has not arrived off the swarm yet, and caching that would answer "this
|
|
87
|
+
// file declares nothing" for the life of the process — which is what
|
|
88
|
+
// decides whether the viewer is offered a soundtrack at all.
|
|
89
|
+
if (Array.isArray(tracks) && tracks.length > 0) {
|
|
90
|
+
this.tracks.set(key, tracks);
|
|
91
|
+
return tracks;
|
|
92
|
+
}
|
|
93
|
+
return Array.isArray(tracks) ? tracks : [];
|
|
94
|
+
} catch (e) {
|
|
95
|
+
logger.warn(`container: readTracks failed for "${params.label}": ${e?.message ?? e}`);
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* What the file declares about itself as a whole — format, duration, and
|
|
102
|
+
* where its own timeline begins.
|
|
103
|
+
*
|
|
104
|
+
* Read once per file, like the track table beside it, and from the same 64 KB
|
|
105
|
+
* of header. A `null` field means the container does not declare it, which is
|
|
106
|
+
* a final answer about the container.
|
|
107
|
+
*
|
|
108
|
+
* @param {object} params - same as getContainer
|
|
109
|
+
* @returns {Promise<import("./container/Container.js").ContainerMediaInfo|null>}
|
|
110
|
+
*/
|
|
111
|
+
async getMediaInfo(params) {
|
|
112
|
+
const container = await this.getContainer(params);
|
|
113
|
+
if (!container) return null;
|
|
114
|
+
try {
|
|
115
|
+
return await container.readMediaInfo();
|
|
116
|
+
} catch (e) {
|
|
117
|
+
logger.warn(`container: readMediaInfo failed for "${params.label}": ${e?.message ?? e}`);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {object} params - same as getContainer
|
|
124
|
+
* @returns {Promise<{times:number[],tolerance:number}|null>}
|
|
125
|
+
*/
|
|
126
|
+
async getKeyframeIndex(params) {
|
|
127
|
+
const container = await this.getContainer(params);
|
|
128
|
+
if (!container) return null;
|
|
129
|
+
try {
|
|
130
|
+
return await container.readKeyframeIndex();
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
forget(sourceKey, fileIndex) {
|
|
137
|
+
if (fileIndex === undefined) {
|
|
138
|
+
for (const k of [...this.cache.keys()]) if (k.startsWith(`${sourceKey}:`)) this.cache.delete(k);
|
|
139
|
+
for (const k of [...this.pending.keys()]) if (k.startsWith(`${sourceKey}:`)) this.pending.delete(k);
|
|
140
|
+
for (const k of [...this.tracks.keys()]) if (k.startsWith(`${sourceKey}:`)) this.tracks.delete(k);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.cache.delete(`${sourceKey}:${fileIndex}`);
|
|
144
|
+
this.pending.delete(`${sourceKey}:${fileIndex}`);
|
|
145
|
+
this.tracks.delete(`${sourceKey}:${fileIndex}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const containerOrchestrator = new ContainerOrchestrator();
|