@torrent-tv/proxy 2.87.10 → 2.87.12
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 +2191 -2181
- 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 +124 -101
- 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/post.js +53 -53
- package/routes/api/sources/files/get.js +100 -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 +136 -129
- package/routes/api/transcode-sessions/post.js +173 -160
- 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 +361 -361
- 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 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- 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 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2184 -2162
- 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 -1083
- 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 -266
- 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 +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- 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 +1146 -1146
- package/services/server/ViewerRequests.js +727 -522
- 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 +620 -0
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +992 -983
- 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 +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -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/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 +365 -365
- 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 +682 -657
- 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 +161 -161
- 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-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 +381 -381
- 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 +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- 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 +323 -0
- 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 +103 -103
- 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 +182 -182
- 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,167 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Subtitle orchestrator — the track list and the cues, behind the
|
|
3
|
-
* Container/Track abstraction.
|
|
4
|
-
*
|
|
5
|
-
* Provides per-file track list and cue streaming, with the same "only already
|
|
6
|
-
* downloaded clusters" rule as before. Controllers (HTTP or data-channel)
|
|
7
|
-
* depend on this, not on the worker module directly. Delegates to
|
|
8
|
-
* `ContainerOrchestrator` for track enumeration so subtitle tracks and their
|
|
9
|
-
* flags come from the unified `ContainerTrack` hierarchy.
|
|
10
|
-
*
|
|
11
|
-
* **The cue reading is HANDED IN, not imported, and that is the layer boundary.**
|
|
12
|
-
* Which clusters may be read, where the cursor stands and what one walk at a
|
|
13
|
-
* time means are the TORRENT's rules, and they live in
|
|
14
|
-
* `torrent-worker/subtitle-cues.js`. This file used to import them, so the media
|
|
15
|
-
* layer named the torrent's worker — the one direction the layers may not go.
|
|
16
|
-
* Now the wiring puts the two together (`server/controllers/SubtitleController.js`) and
|
|
17
|
-
* this can be exercised with four plain functions.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { logger } from "../../utils/logger.js";
|
|
21
|
-
import { SubtitleFileContainer } from "./container/SubtitleFileContainer.js";
|
|
22
|
-
import { TextSubtitleTrack } from "./tracks/TextSubtitleTrack.js";
|
|
23
|
-
import { detectLanguage } from "./tracks/language-detect.js";
|
|
24
|
-
|
|
25
|
-
export class SubtitleOrchestrator {
|
|
26
|
-
/**
|
|
27
|
-
* @param {import("./ContainerOrchestrator.js").ContainerOrchestrator} containerOrchestrator
|
|
28
|
-
* @param {object} [cues] - How the torrent reads subtitle cues: `warm`,
|
|
29
|
-
* `tracksOf`, `declaredTracksOf`, `forget`. Absent, this answers from the
|
|
30
|
-
* containers alone, which is what a proxy with no torrent behind it can do.
|
|
31
|
-
*/
|
|
32
|
-
constructor(containerOrchestrator, cues = {}) {
|
|
33
|
-
this.containers = containerOrchestrator;
|
|
34
|
-
this.cues = cues ?? {};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Tracks for menu — text tracks via domain, enriched with ContainerTrack flags.
|
|
39
|
-
* Falls back to container tracks when domain has no plan yet.
|
|
40
|
-
* @param {object} torrent
|
|
41
|
-
* @param {number} fileIndex
|
|
42
|
-
* @param {string} sourceKey
|
|
43
|
-
* @param {(start:number,end:number)=>Promise<Buffer|null>} [readRange]
|
|
44
|
-
* @param {number} [fileSize]
|
|
45
|
-
* @returns {Promise<import("./tracks/index.js").ContainerTrack[]>}
|
|
46
|
-
*/
|
|
47
|
-
async getTracks(torrent, fileIndex, sourceKey, readRange, fileSize) {
|
|
48
|
-
try {
|
|
49
|
-
const domain = await this.cues.tracksOf?.(torrent, fileIndex, sourceKey);
|
|
50
|
-
if (Array.isArray(domain) && domain.length > 0) return domain;
|
|
51
|
-
} catch {}
|
|
52
|
-
if (readRange && Number.isFinite(fileSize)) {
|
|
53
|
-
try {
|
|
54
|
-
const tracks = await this.containers.getTracks({ sourceKey, fileIndex, readRange, fileSize, label: torrent?.files?.[fileIndex]?.name ?? "" });
|
|
55
|
-
return tracks.filter((t) => t.type === "subtitle");
|
|
56
|
-
} catch {}
|
|
57
|
-
}
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Declared subtitle tracks in container order (including image tracks) — for declaredIndex alignment.
|
|
63
|
-
*/
|
|
64
|
-
async getDeclaredTracks(torrent, fileIndex, sourceKey) {
|
|
65
|
-
try {
|
|
66
|
-
return (await this.cues.declaredTracksOf?.(torrent, fileIndex, sourceKey)) ?? [];
|
|
67
|
-
} catch {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* The cues a file already holds for one track.
|
|
74
|
-
*
|
|
75
|
-
* The PULL path: a browser asking `/api/subtitles` rather than being pushed
|
|
76
|
-
* to. It runs the same walk as the push, on this thread — it used to be sent
|
|
77
|
-
* to the torrent thread as a command, and when that command went the guard
|
|
78
|
-
* here answered an EMPTY document instead of failing, which is
|
|
79
|
-
* indistinguishable from a file that genuinely holds no cues. That is the one
|
|
80
|
-
* failure this whole path is written to avoid, so an absent walk says so.
|
|
81
|
-
*
|
|
82
|
-
* @param {object} torrent
|
|
83
|
-
* @param {number} fileIndex
|
|
84
|
-
* @param {string} sourceKey
|
|
85
|
-
* @param {number} trackNumber
|
|
86
|
-
* @returns {Promise<{ cues: object[], coveredClusters: number, indexedClusters: number, track: object | null }>}
|
|
87
|
-
*/
|
|
88
|
-
async getCues(torrent, fileIndex, sourceKey, trackNumber) {
|
|
89
|
-
const empty = () => ({ cues: [], coveredClusters: 0, indexedClusters: 0, track: null });
|
|
90
|
-
if (typeof this.cues.held !== "function") {
|
|
91
|
-
logger.warn(
|
|
92
|
-
"subtitle-orchestrator: no walk was supplied, so no cue can be read — " +
|
|
93
|
-
"an empty document here would be indistinguishable from a file with none"
|
|
94
|
-
);
|
|
95
|
-
return empty();
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const answer = await this.cues.held(torrent, fileIndex, sourceKey, trackNumber);
|
|
99
|
-
return answer ?? empty();
|
|
100
|
-
} catch (e) {
|
|
101
|
-
logger.warn(`subtitle-orchestrator: getCues failed: ${e?.message ?? e}`);
|
|
102
|
-
return empty();
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Warm all subtitle tracks of a file — called periodically and on verified pieces.
|
|
108
|
-
* Returns per-track fresh cues for push.
|
|
109
|
-
*/
|
|
110
|
-
async warm(torrent, fileIndex, sourceKey) {
|
|
111
|
-
try {
|
|
112
|
-
return (await this.cues.warm?.(torrent, fileIndex, sourceKey)) ?? [];
|
|
113
|
-
} catch {
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* A subtitle FILE as WebVTT, and the language its words are in.
|
|
120
|
-
*
|
|
121
|
-
* The language is read from the CONVERTED document, not from the file. The
|
|
122
|
-
* conversion has already dropped everything that is not the words — and on
|
|
123
|
-
* an ASS file that is half of it, in Latin letters, which is what made a
|
|
124
|
-
* Russian track answer `en` (field 2026-09-01,
|
|
125
|
-
* `research/subtitle-language-ass-markup-2026-09-01.md`).
|
|
126
|
-
*
|
|
127
|
-
* @param {Buffer} bytes
|
|
128
|
-
* @param {string} extension - With its dot, lower case.
|
|
129
|
-
* @returns {{ vtt: string, language: string | null } | null} Null for a
|
|
130
|
-
* format this cannot convert.
|
|
131
|
-
*/
|
|
132
|
-
fileAsVtt(bytes, extension) {
|
|
133
|
-
const text = SubtitleFileContainer.decodeBytes(bytes);
|
|
134
|
-
const vtt = SubtitleFileContainer.toVtt(text, extension);
|
|
135
|
-
if (!vtt) {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
return { vtt, language: TextSubtitleTrack.detectLanguageFromVtt(vtt) };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Cues held for an embedded track as WebVTT, and the language of the track.
|
|
143
|
-
*
|
|
144
|
-
* Two things this reads, and each of them was wrong before 2.68.1. It reads
|
|
145
|
-
* the cues through `finalizeCues`, so what reaches the detector is the words
|
|
146
|
-
* and not ASS's `{\…}` override groups, which are Latin on a Russian track.
|
|
147
|
-
* And it reads EVERY cue held so far, not the subset being sent: a
|
|
148
|
-
* re-subscription after a reconnect asks only for what the page missed,
|
|
149
|
-
* which can be three lines, and three lines are not a sample of a language.
|
|
150
|
-
*
|
|
151
|
-
* @param {object[]} sending - The cues this answer carries.
|
|
152
|
-
* @param {object[]} held - Every cue held for the track so far.
|
|
153
|
-
* @param {string} codecId
|
|
154
|
-
* @returns {{ vtt: string, language: string | null }}
|
|
155
|
-
*/
|
|
156
|
-
cuesAsVtt(sending, held, codecId) {
|
|
157
|
-
return {
|
|
158
|
-
vtt: TextSubtitleTrack.cuesToVtt(sending, codecId),
|
|
159
|
-
language: detectLanguage(TextSubtitleTrack.finalizeCues(held, codecId).map((cue) => cue.text).join("\n"))
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
forget(sourceKey, fileIndex) {
|
|
164
|
-
this.cues.forget?.(sourceKey, fileIndex);
|
|
165
|
-
this.containers.forget(sourceKey, fileIndex);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file Subtitle orchestrator — the track list and the cues, behind the
|
|
3
|
+
* Container/Track abstraction.
|
|
4
|
+
*
|
|
5
|
+
* Provides per-file track list and cue streaming, with the same "only already
|
|
6
|
+
* downloaded clusters" rule as before. Controllers (HTTP or data-channel)
|
|
7
|
+
* depend on this, not on the worker module directly. Delegates to
|
|
8
|
+
* `ContainerOrchestrator` for track enumeration so subtitle tracks and their
|
|
9
|
+
* flags come from the unified `ContainerTrack` hierarchy.
|
|
10
|
+
*
|
|
11
|
+
* **The cue reading is HANDED IN, not imported, and that is the layer boundary.**
|
|
12
|
+
* Which clusters may be read, where the cursor stands and what one walk at a
|
|
13
|
+
* time means are the TORRENT's rules, and they live in
|
|
14
|
+
* `torrent-worker/subtitle-cues.js`. This file used to import them, so the media
|
|
15
|
+
* layer named the torrent's worker — the one direction the layers may not go.
|
|
16
|
+
* Now the wiring puts the two together (`server/controllers/SubtitleController.js`) and
|
|
17
|
+
* this can be exercised with four plain functions.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { logger } from "../../utils/logger.js";
|
|
21
|
+
import { SubtitleFileContainer } from "./container/SubtitleFileContainer.js";
|
|
22
|
+
import { TextSubtitleTrack } from "./tracks/TextSubtitleTrack.js";
|
|
23
|
+
import { detectLanguage } from "./tracks/language-detect.js";
|
|
24
|
+
|
|
25
|
+
export class SubtitleOrchestrator {
|
|
26
|
+
/**
|
|
27
|
+
* @param {import("./ContainerOrchestrator.js").ContainerOrchestrator} containerOrchestrator
|
|
28
|
+
* @param {object} [cues] - How the torrent reads subtitle cues: `warm`,
|
|
29
|
+
* `tracksOf`, `declaredTracksOf`, `forget`. Absent, this answers from the
|
|
30
|
+
* containers alone, which is what a proxy with no torrent behind it can do.
|
|
31
|
+
*/
|
|
32
|
+
constructor(containerOrchestrator, cues = {}) {
|
|
33
|
+
this.containers = containerOrchestrator;
|
|
34
|
+
this.cues = cues ?? {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tracks for menu — text tracks via domain, enriched with ContainerTrack flags.
|
|
39
|
+
* Falls back to container tracks when domain has no plan yet.
|
|
40
|
+
* @param {object} torrent
|
|
41
|
+
* @param {number} fileIndex
|
|
42
|
+
* @param {string} sourceKey
|
|
43
|
+
* @param {(start:number,end:number)=>Promise<Buffer|null>} [readRange]
|
|
44
|
+
* @param {number} [fileSize]
|
|
45
|
+
* @returns {Promise<import("./tracks/index.js").ContainerTrack[]>}
|
|
46
|
+
*/
|
|
47
|
+
async getTracks(torrent, fileIndex, sourceKey, readRange, fileSize) {
|
|
48
|
+
try {
|
|
49
|
+
const domain = await this.cues.tracksOf?.(torrent, fileIndex, sourceKey);
|
|
50
|
+
if (Array.isArray(domain) && domain.length > 0) return domain;
|
|
51
|
+
} catch {}
|
|
52
|
+
if (readRange && Number.isFinite(fileSize)) {
|
|
53
|
+
try {
|
|
54
|
+
const tracks = await this.containers.getTracks({ sourceKey, fileIndex, readRange, fileSize, label: torrent?.files?.[fileIndex]?.name ?? "" });
|
|
55
|
+
return tracks.filter((t) => t.type === "subtitle");
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Declared subtitle tracks in container order (including image tracks) — for declaredIndex alignment.
|
|
63
|
+
*/
|
|
64
|
+
async getDeclaredTracks(torrent, fileIndex, sourceKey) {
|
|
65
|
+
try {
|
|
66
|
+
return (await this.cues.declaredTracksOf?.(torrent, fileIndex, sourceKey)) ?? [];
|
|
67
|
+
} catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The cues a file already holds for one track.
|
|
74
|
+
*
|
|
75
|
+
* The PULL path: a browser asking `/api/subtitles` rather than being pushed
|
|
76
|
+
* to. It runs the same walk as the push, on this thread — it used to be sent
|
|
77
|
+
* to the torrent thread as a command, and when that command went the guard
|
|
78
|
+
* here answered an EMPTY document instead of failing, which is
|
|
79
|
+
* indistinguishable from a file that genuinely holds no cues. That is the one
|
|
80
|
+
* failure this whole path is written to avoid, so an absent walk says so.
|
|
81
|
+
*
|
|
82
|
+
* @param {object} torrent
|
|
83
|
+
* @param {number} fileIndex
|
|
84
|
+
* @param {string} sourceKey
|
|
85
|
+
* @param {number} trackNumber
|
|
86
|
+
* @returns {Promise<{ cues: object[], coveredClusters: number, indexedClusters: number, track: object | null }>}
|
|
87
|
+
*/
|
|
88
|
+
async getCues(torrent, fileIndex, sourceKey, trackNumber) {
|
|
89
|
+
const empty = () => ({ cues: [], coveredClusters: 0, indexedClusters: 0, track: null });
|
|
90
|
+
if (typeof this.cues.held !== "function") {
|
|
91
|
+
logger.warn(
|
|
92
|
+
"subtitle-orchestrator: no walk was supplied, so no cue can be read — " +
|
|
93
|
+
"an empty document here would be indistinguishable from a file with none"
|
|
94
|
+
);
|
|
95
|
+
return empty();
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const answer = await this.cues.held(torrent, fileIndex, sourceKey, trackNumber);
|
|
99
|
+
return answer ?? empty();
|
|
100
|
+
} catch (e) {
|
|
101
|
+
logger.warn(`subtitle-orchestrator: getCues failed: ${e?.message ?? e}`);
|
|
102
|
+
return empty();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Warm all subtitle tracks of a file — called periodically and on verified pieces.
|
|
108
|
+
* Returns per-track fresh cues for push.
|
|
109
|
+
*/
|
|
110
|
+
async warm(torrent, fileIndex, sourceKey) {
|
|
111
|
+
try {
|
|
112
|
+
return (await this.cues.warm?.(torrent, fileIndex, sourceKey)) ?? [];
|
|
113
|
+
} catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A subtitle FILE as WebVTT, and the language its words are in.
|
|
120
|
+
*
|
|
121
|
+
* The language is read from the CONVERTED document, not from the file. The
|
|
122
|
+
* conversion has already dropped everything that is not the words — and on
|
|
123
|
+
* an ASS file that is half of it, in Latin letters, which is what made a
|
|
124
|
+
* Russian track answer `en` (field 2026-09-01,
|
|
125
|
+
* `research/subtitle-language-ass-markup-2026-09-01.md`).
|
|
126
|
+
*
|
|
127
|
+
* @param {Buffer} bytes
|
|
128
|
+
* @param {string} extension - With its dot, lower case.
|
|
129
|
+
* @returns {{ vtt: string, language: string | null } | null} Null for a
|
|
130
|
+
* format this cannot convert.
|
|
131
|
+
*/
|
|
132
|
+
fileAsVtt(bytes, extension) {
|
|
133
|
+
const text = SubtitleFileContainer.decodeBytes(bytes);
|
|
134
|
+
const vtt = SubtitleFileContainer.toVtt(text, extension);
|
|
135
|
+
if (!vtt) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return { vtt, language: TextSubtitleTrack.detectLanguageFromVtt(vtt) };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Cues held for an embedded track as WebVTT, and the language of the track.
|
|
143
|
+
*
|
|
144
|
+
* Two things this reads, and each of them was wrong before 2.68.1. It reads
|
|
145
|
+
* the cues through `finalizeCues`, so what reaches the detector is the words
|
|
146
|
+
* and not ASS's `{\…}` override groups, which are Latin on a Russian track.
|
|
147
|
+
* And it reads EVERY cue held so far, not the subset being sent: a
|
|
148
|
+
* re-subscription after a reconnect asks only for what the page missed,
|
|
149
|
+
* which can be three lines, and three lines are not a sample of a language.
|
|
150
|
+
*
|
|
151
|
+
* @param {object[]} sending - The cues this answer carries.
|
|
152
|
+
* @param {object[]} held - Every cue held for the track so far.
|
|
153
|
+
* @param {string} codecId
|
|
154
|
+
* @returns {{ vtt: string, language: string | null }}
|
|
155
|
+
*/
|
|
156
|
+
cuesAsVtt(sending, held, codecId) {
|
|
157
|
+
return {
|
|
158
|
+
vtt: TextSubtitleTrack.cuesToVtt(sending, codecId),
|
|
159
|
+
language: detectLanguage(TextSubtitleTrack.finalizeCues(held, codecId).map((cue) => cue.text).join("\n"))
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
forget(sourceKey, fileIndex) {
|
|
164
|
+
this.cues.forget?.(sourceKey, fileIndex);
|
|
165
|
+
this.containers.forget(sourceKey, fileIndex);
|
|
166
|
+
}
|
|
167
|
+
}
|