@torrent-tv/proxy 2.86.0 → 2.87.1
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/CHANGELOG.md +2142 -1915
- package/CLAUDE.md +322 -197
- package/assets/calibration/NOTICE.md +2 -2
- package/bin/cli.js +684 -616
- package/biome.json +409 -233
- package/docs/browser-proxy-contract.md +94 -0
- package/docs/container-architecture.md +218 -213
- package/docs/download-architecture.md +7 -7
- package/docs/encode-architecture.md +287 -11
- package/docs/encode-run-state.md +2 -2
- package/docs/logs.md +97 -97
- package/docs/{disk-architecture.md → storage-architecture.md} +107 -1
- package/openspec/changes/adaptive-bitrate/design.md +2 -2
- package/openspec/changes/adaptive-bitrate/proposal.md +1 -1
- package/openspec/changes/adaptive-bitrate/tasks.md +1 -1
- package/openspec/changes/chunked-request-bodies/design.md +1 -1
- package/openspec/changes/chunked-request-bodies/proposal.md +1 -1
- package/openspec/changes/chunked-request-bodies/tasks.md +1 -1
- package/openspec/changes/cold-start/design.md +1 -1
- package/openspec/changes/disk-cap/proposal.md +1 -1
- package/openspec/changes/proxy-observability/proposal.md +2 -2
- package/openspec/changes/track-selection/proposal.md +1 -1
- package/openspec/changes/transcode-quality/proposal.md +1 -1
- package/package.json +2 -2
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/routes/api/playback-plan/post.js +2 -2
- package/routes/api/sources/files/get.js +1 -1
- package/routes/api/sources/stats/get.js +1 -1
- package/routes/api/sources/warm/post.js +10 -3
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +3 -3
- package/routes/api/transcode-sessions/net-report/post.js +129 -94
- package/routes/api/transcode-sessions/post.js +160 -139
- package/routes/api/transcode-sessions/progress/get.js +3 -3
- package/routes/api/transcode-sessions/release/post.js +3 -3
- package/routes/api/transcode-sessions/seek/post.js +12 -3
- package/routes/stream/get.js +2 -2
- package/routes/transcode/audio-file/get.js +15 -6
- package/routes/transcode/audio-warm/get.js +9 -6
- package/routes/transcode/session-file/get.js +52 -268
- package/routes/transcode/variant-file/get.js +46 -7
- package/routes/transcode/variant-warm/get.js +19 -6
- package/scripts/render-run-graph.js +1 -1
- package/server.js +720 -465
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -0
- package/services/encode/EncodeAdmission.js +332 -0
- package/services/{orchestrators → encode}/EncodeOrchestrator.js +307 -29
- package/services/encode/EncodeRun.js +16 -9
- package/services/encode/EncodeRuns.js +1229 -0
- package/services/encode/Encoder.js +18 -2
- package/services/encode/EncoderSelection.js +38 -0
- package/services/encode/LocalObservations.js +359 -0
- package/services/encode/NvencEncoder.js +5 -0
- package/services/encode/OutputOpening.js +755 -0
- package/services/encode/OutputTimes.js +579 -0
- package/services/encode/Renditions.js +2162 -0
- package/services/encode/RunProgress.js +84 -0
- package/services/encode/SoftwareEncoder.js +12 -7
- package/services/encode/args.js +402 -186
- package/services/encode/calibration.js +361 -0
- package/services/encode/contention.js +128 -128
- package/services/encode/encode-run-state.js +16 -20
- package/services/encode/fingerprint.js +322 -0
- package/services/{hwaccel.js → encode/hwaccel.js} +82 -42
- package/services/{learned-median.js → encode/learned-median.js} +11 -0
- package/services/encode/output/EncodedOutput.js +57 -0
- package/services/encode/output/Output.js +131 -0
- package/services/{output/LiveOutputs.js → encode/output/OutputCatalog.js} +141 -40
- package/services/encode/output/OutputSpec.js +390 -0
- package/services/{output → encode/output}/Timeline.js +12 -37
- package/services/encode/output/index.js +20 -0
- package/services/encode/output/ladder.js +32 -0
- package/services/{output → encode/output}/playlists.js +3 -0
- package/services/encode/output-key-format.js +21 -0
- package/services/encode/quality/EncodeCost.js +1083 -0
- package/services/encode/quality/HostLoad.js +525 -0
- package/services/encode/quality/HostTimings.js +249 -0
- package/services/encode/quality/OutputQualityState.js +28 -0
- package/services/encode/quality/QualityController.js +935 -0
- package/services/encode/quality/QualityOffer.js +416 -0
- package/services/encode/quality/drain-threat.js +47 -0
- package/services/encode/quality/link-budget.js +254 -0
- package/services/encode/quality/output-format.js +266 -0
- package/services/encode/quality/serving-output.js +216 -0
- package/services/encode/quality/visible-rung.js +57 -0
- package/services/encode/run-command.js +23 -20
- package/services/encode/run-inputs.js +42 -0
- package/services/{segment-formats → encode/segment-formats}/fmp4.js +63 -0
- package/services/{segment-formats → encode/segment-formats}/index.js +3 -3
- package/services/encode/segment-formats/init-compat.js +324 -0
- package/services/{segment-formats → encode/segment-formats}/mp4-boxes.js +8 -3
- package/services/encode/throughput.js +97 -0
- package/services/{orchestrators → media}/ContainerOrchestrator.js +45 -7
- package/services/media/KeyframeTables.js +316 -0
- package/services/{source → media}/SourceFile.js +7 -55
- package/services/{torrent-worker/subtitle-cues.js → media/SubtitleCues.js} +103 -132
- package/services/media/SubtitleOrchestrator.js +167 -0
- package/services/{audio-inventory.js → media/audio-inventory.js} +7 -1
- package/services/{container → media/container}/AviContainer.js +266 -266
- package/services/{container → media/container}/Container.js +596 -601
- package/services/media/container/ContainerFactory.js +56 -0
- package/services/media/container/KeyframeTable.js +272 -0
- package/services/{container → media/container}/MatroskaContainer.js +1485 -1485
- package/services/{container → media/container}/Mp4Container.js +1244 -1244
- package/services/{container → media/container}/SubtitleFileContainer.js +322 -322
- package/services/media/keyframe-probe.js +157 -0
- package/services/media/media-info-probe.js +93 -0
- package/services/{playback-planner.js → media/playback-planner.js} +82 -39
- package/services/{tracks → media/tracks}/AudioTrack.js +131 -131
- package/services/{tracks → media/tracks}/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -0
- package/services/server/SegmentServing.js +1146 -0
- package/services/server/ViewerRequests.js +522 -0
- package/services/server/controllers/PlaybackController.js +36 -0
- package/services/{controllers → server/controllers}/SubtitleController.js +17 -33
- package/services/server/http-responses.js +60 -0
- package/services/server/transcode-session-files.js +325 -0
- package/services/server/wire-outputs.js +983 -0
- package/services/storage/Diagnostics.js +202 -0
- package/services/storage/MachineBudget.js +235 -0
- package/services/{piece-store → storage}/allowance.js +107 -107
- package/services/{core-dumps.js → storage/core-dumps.js} +1 -1
- package/services/{files → storage/files}/CompletedFiles.js +116 -1
- package/services/{disk → storage}/free.js +33 -0
- package/services/storage/machine-memory.js +56 -0
- package/services/{memory-report.js → storage/memory-report.js} +575 -596
- package/services/{piece-store → storage/piece-store}/piece-disk-store.js +0 -7
- package/services/{piece-store → storage/piece-store}/shared-piece-store.js +28 -21
- package/services/{encode → storage/segment-store}/SegmentStore.js +395 -8
- package/services/storage/wire.js +175 -0
- package/services/{download → torrent/download}/SwarmSelection.js +2 -2
- package/services/{download → torrent/download}/registry.js +2 -2
- package/services/torrent/files.js +1 -1
- package/services/torrent/piece-store-of.js +29 -0
- package/services/{torrent-pool.js → torrent/torrent-pool.js} +26 -119
- package/services/{torrent-worker → torrent/worker}/background-fill.js +2 -2
- package/services/{torrent-worker → torrent/worker}/channel.js +271 -250
- package/services/{torrent-worker → torrent/worker}/client.js +77 -58
- package/services/torrent/worker/held-bytes.js +155 -0
- package/services/{torrent-worker → torrent/worker}/install-webrtc-shim.js +1 -1
- package/services/{torrent-worker → torrent/worker}/piece-reader.js +21 -30
- package/services/{torrent-worker → torrent/worker}/pool-adapter.js +101 -137
- package/services/{torrent-worker → torrent/worker}/protocol.js +26 -30
- package/services/torrent/worker/resume-warm.js +107 -0
- package/services/{torrent-worker → torrent/worker}/webrtc-shim.js +2 -2
- package/services/{torrent-worker → torrent/worker}/worker.js +68 -159
- package/services/{data-channel-handler.js → transport/data-channel-handler.js} +1356 -1356
- package/services/{delivery-probe.js → transport/delivery-probe.js} +842 -842
- package/services/{health-collector.js → transport/health-collector.js} +6 -35
- package/services/{packet-witness.js → transport/packet-witness.js} +808 -784
- package/services/{port-mapper.js → transport/port-mapper.js} +1 -1
- package/services/{usrsctp-state.js → transport/usrsctp-state.js} +1 -1
- package/services/{webrtc-manager.js → transport/webrtc-manager.js} +1 -1
- package/services/viewer/Assignments.js +340 -0
- package/services/{priority → viewer}/PriorityMap.js +390 -390
- package/services/{priority → viewer}/PriorityOrchestrator.js +325 -320
- package/services/viewer/Viewer.js +657 -483
- package/services/viewer/Viewers.js +368 -257
- package/services/viewer/active-output.js +37 -54
- package/services/viewer/audio-start.js +4 -3
- package/services/viewer/buffer-trend.js +81 -0
- package/services/viewer/choices.js +647 -0
- package/services/viewer/positions.js +126 -120
- package/services/viewer/report-intake.js +46 -45
- package/test/asset-paths.test.js +28 -0
- package/test/audio-inventory.test.js +3 -3
- package/test/audio-track-bitrate.test.js +40 -0
- package/test/auto-quality-step.test.js +707 -515
- package/test/available-share.test.js +1 -1
- package/test/background-fill.test.js +1 -1
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +1 -1
- package/test/concurrent-cost.test.js +13 -3
- package/test/container-media-info.test.js +3 -3
- package/test/core-dumps.test.js +1 -1
- package/test/coverage-follows-the-disk.test.js +46 -9
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -0
- package/test/cut-times-timeline.test.js +2 -2
- package/test/cuts-follow-published-grid.test.js +9 -8
- package/test/data-channel-frame.test.js +1 -1
- package/test/dead-torrent-handle.test.js +1 -1
- package/test/declared-rates.test.js +2 -2
- package/test/declared-tracks.test.js +2 -2
- package/test/decode-cost-fit.test.js +1 -1
- package/test/decode-cost.test.js +34 -25
- package/test/delivery-probe.test.js +1 -1
- package/test/demand-register.test.js +4 -4
- package/test/dht-bootstrap.test.js +1 -1
- package/test/drain-threat.test.js +65 -0
- package/test/duplicate-add.test.js +1 -1
- package/test/encode-admission.test.js +146 -0
- package/test/encode-cost.test.js +199 -10
- package/test/encode-orchestrator.test.js +39 -1
- package/test/encode-plan-viewers.test.js +2 -2
- package/test/encode-run.test.js +2 -2
- package/test/encode-slope.test.js +1 -1
- package/test/encoded-output.test.js +39 -0
- package/test/encoder-readings.test.js +1 -1
- package/test/every-claimant-is-told-its-share.test.js +152 -0
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -0
- package/test/failed-start-breaker.test.js +81 -30
- package/test/fastest-wires.test.js +1 -1
- package/test/ffmpeg-stream-counts.test.js +1 -1
- package/test/file-edges.test.js +3 -3
- package/test/fmp4-self-contained.test.js +1 -1
- package/test/fmp4-timestamps.test.js +1 -1
- package/test/fmp4-track-completeness.test.js +1 -1
- package/test/format-keyed-outputs.test.js +78 -0
- package/test/health-metrics.test.js +3 -2
- package/test/held-request-width.test.js +22 -23
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -0
- package/test/helpers/manager.js +57 -0
- package/test/helpers/output-spec.js +63 -0
- package/test/host-load.test.js +1 -1
- package/test/index-accuracy-is-about-the-file.test.js +128 -0
- package/test/init-compat.test.js +219 -0
- package/test/input-lost-quiets-the-plan.test.js +1 -1
- package/test/input-unavailable.test.js +1 -1
- package/test/keeping-period.test.js +4 -4
- package/test/keyframe-index-accuracy.test.js +97 -45
- package/test/keyframe-table-budget.test.js +28 -33
- package/test/keyframes-belong-to-the-file.test.js +174 -57
- package/test/learned-median.test.js +1 -1
- package/test/letting-go-asks-who-is-watching.test.js +191 -0
- package/test/limit-row-by-area.test.js +60 -0
- package/test/link-budget.test.js +111 -0
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -0
- package/test/look-ahead-coverage.test.js +1 -1
- package/test/manager-surface.test.js +294 -0
- package/test/matroska-blocks.test.js +0 -0
- package/test/matroska-cues-track.test.js +1 -1
- package/test/memory-budget.test.js +2 -2
- package/test/mp4-composition-times.test.js +0 -0
- package/test/mp4-subtitles.test.js +1 -1
- package/test/one-authority.test.js +45 -41
- package/test/one-budget.test.js +149 -0
- package/test/one-output-one-session.test.js +204 -0
- package/test/{disk-space.test.js → one-resource-divided.test.js} +30 -10
- package/test/one-serving-output-per-height.test.js +458 -0
- package/test/orchestrator-wired.test.js +46 -45
- package/test/output-catalog.test.js +39 -0
- package/test/output-shape.test.js +4 -51
- package/test/output-spec.test.js +251 -157
- package/test/output-speed.test.js +17 -15
- package/test/outputs-being-prepared.test.js +40 -0
- package/test/packet-witness-ring.test.js +2 -2
- package/test/packet-witness.test.js +1 -1
- package/test/piece-disk-store.test.js +2 -2
- package/test/piece-from-whole-file.test.js +1 -1
- package/test/piece-lru.test.js +1 -1
- package/test/piece-reader.test.js +2 -2
- package/test/piece-store-eviction.test.js +1 -1
- package/test/piece-store-never-refuses.test.js +1 -1
- package/test/piece-store-reservations.test.js +1 -1
- package/test/piece-store-slow-disk.test.js +1 -1
- package/test/piece-tail.test.js +1 -1
- package/test/plan-host-timings.test.js +1 -1
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +4 -4
- package/test/priority-map-emptied.test.js +204 -205
- package/test/priority-map-per-output.test.js +215 -209
- package/test/priority-map.test.js +1 -1
- package/test/probe-wedge-certainty.test.js +1 -1
- package/test/produced-copy-choice.test.js +15 -60
- package/test/quality-offer.test.js +162 -0
- package/test/quality-variants.test.js +1229 -1167
- package/test/rate-control-in-the-output.test.js +180 -0
- package/test/read-survives-withdrawal.test.js +2 -2
- package/test/read-window.test.js +10 -3
- package/test/resume-warm.test.js +1 -1
- package/test/run-inputs.test.js +80 -0
- package/test/run-position-follows-published-grid.test.js +13 -11
- package/test/run-progress.test.js +65 -0
- package/test/same-height-switch.test.js +664 -0
- package/test/seek-frees-the-viewers-requests.test.js +217 -0
- package/test/seek-landing.test.js +13 -13
- package/test/seek-target-not-superseded.test.js +28 -29
- package/test/segment-closed-on-both-branches.test.js +96 -0
- package/test/segment-cut-times.test.js +1 -1
- package/test/segment-hold-supersede.test.js +28 -35
- package/test/segment-judged-by-its-own-header.test.js +221 -0
- package/test/segment-serve-wiring.test.js +130 -39
- package/test/segment-store-eviction.test.js +22 -7
- package/test/segment-store.test.js +98 -14
- package/test/segment-true-start.test.js +1 -1
- package/test/segments-are-shared.test.js +17 -16
- package/test/send-chunk-bytes.test.js +1 -1
- package/test/serving-existing-output.test.js +102 -0
- package/test/serving-output.test.js +102 -0
- package/test/shared-piece-store.test.js +1 -1
- package/test/source-file.test.js +1 -1
- package/test/stale-request-after-seek.test.js +17 -21
- package/test/startup-readings.test.js +10 -5
- package/test/subtitle-cue-framing.test.js +5 -5
- package/test/subtitle-cue-source.test.js +36 -38
- package/test/subtitle-cue-walk.test.js +11 -10
- package/test/subtitle-defaults.test.js +1 -1
- package/test/subtitle-language.test.js +4 -4
- package/test/subtitle-track-numbering.test.js +5 -4
- package/test/supply-margin.test.js +1 -1
- package/test/swarm-follows-readers.test.js +3 -3
- package/test/swarm-reach.test.js +1 -1
- package/test/swarm-selection.test.js +3 -3
- package/test/tail-duplication.test.js +1 -1
- package/test/the-channel-has-both-ends.test.js +91 -0
- package/test/the-torrent-thread-serves-bytes.test.js +107 -0
- package/test/thread-pool.test.js +3 -3
- package/test/timeline.test.js +1 -12
- package/test/torrent-cost.test.js +1 -1
- package/test/tracks-begin-together.test.js +38 -24
- package/test/tunnel-renewal.test.js +1 -1
- package/test/two-viewers-one-picture.test.js +120 -87
- package/test/upload-hurry.test.js +3 -3
- package/test/usrsctp-state.test.js +1 -1
- package/test/video-facts.test.js +1 -1
- package/test/viewer-assignments.test.js +143 -0
- package/test/viewer-outputs.test.js +60 -52
- package/test/viewer-presence.test.js +22 -25
- package/test/viewer-requests-admission.test.js +89 -0
- package/test/viewer-states.test.js +189 -202
- package/test/viewer-subtitle-subscription.test.js +1 -1
- package/test/viewer.test.js +139 -136
- package/test/viewing-generations.test.js +390 -0
- package/test/visible-rung.test.js +37 -0
- package/test/wait-ledger.test.js +1 -1
- package/test/webrtc-shim.test.js +3 -3
- package/test/wedge-certainty.test.js +2 -2
- package/test/withdraw-piece-claim.test.js +2 -2
- package/test/worker-channel.test.js +1 -31
- package/utils/client-log-file.js +151 -151
- package/scripts/read-container-index.mjs +0 -108
- package/services/container/ContainerFactory.js +0 -122
- package/services/controllers/PlaybackController.js +0 -33
- package/services/disk/DiskSpace.js +0 -150
- package/services/disk/wire.js +0 -61
- package/services/hls-session-manager.js +0 -9608
- package/services/orchestrators/SubtitleOrchestrator.js +0 -160
- package/services/output/Output.js +0 -94
- package/services/output/OutputSpec.js +0 -202
- package/services/output/index.js +0 -1
- package/services/output/ladder.js +0 -26
- package/services/produced-index.js +0 -222
- package/services/quality/EncodeCost.js +0 -555
- package/services/torrent-worker/container-tracks.js +0 -414
- package/services/viewer/link-readings.js +0 -56
- package/test/produced-index.test.js +0 -142
- /package/{services → bin}/thread-pool.js +0 -0
- /package/services/{available-share.js → encode/available-share.js} +0 -0
- /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
- /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
- /package/services/{host-load.js → encode/host-load.js} +0 -0
- /package/services/{output → encode/output}/cut-grid.js +0 -0
- /package/services/{output → encode/output}/rates.js +0 -0
- /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
- /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
- /package/services/{container → media/container}/ebml-reader.js +0 -0
- /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
- /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
- /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
- /package/services/{tracks → media/tracks}/index.js +0 -0
- /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
- /package/services/{disk → storage}/keep.js +0 -0
- /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
- /package/services/{disk → storage}/returns.js +0 -0
- /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
- /package/services/{demand → torrent/demand}/Urgency.js +0 -0
- /package/services/{demand → torrent/demand}/Window.js +0 -0
- /package/services/{demand → torrent/demand}/index.js +0 -0
- /package/services/{demand → torrent/demand}/pieces.js +0 -0
- /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
- /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
- /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
- /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
- /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
- /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
- /package/services/{registry-api.js → transport/registry-api.js} +0 -0
- /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
- /package/services/{priority → viewer}/WaitLedger.js +0 -0
|
@@ -25,21 +25,17 @@ import "./install-webrtc-shim.js";
|
|
|
25
25
|
import { parentPort, workerData } from "node:worker_threads";
|
|
26
26
|
import { createSendStream } from "./channel.js";
|
|
27
27
|
import { readFragments, supplyFiguresFor } from "./piece-reader.js";
|
|
28
|
-
import { cuesHeldFor, declaredSubtitleTracksOf, subtitleTracksOf, warmSubtitleCues } from "./subtitle-cues.js";
|
|
29
28
|
import {
|
|
30
|
-
CONTAINER_HEAD_BYTES,
|
|
31
|
-
containerKeyframesOf,
|
|
32
|
-
containerMediaInfoOf,
|
|
33
|
-
containerTracksOf,
|
|
34
29
|
warmResumePosition
|
|
35
|
-
} from "./
|
|
30
|
+
} from "./resume-warm.js";
|
|
36
31
|
import { fillFileInBackground } from "./background-fill.js";
|
|
37
32
|
import { demandFor } from "../download/registry.js";
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
33
|
+
import { heldRangesOf, readHeldBytes } from "./held-bytes.js";
|
|
34
|
+
import { CompletedFiles, completedFilesRoot } from "../../storage/files/CompletedFiles.js";
|
|
35
|
+
import { pieceFromWholeFiles, pieceIsInWholeFiles } from "../../storage/files/piece-from-whole-file.js";
|
|
40
36
|
import { Command, Event } from "./protocol.js";
|
|
41
|
-
import { startMemoryReport, WORKER_MEMORY_SAMPLE_MS } from "
|
|
42
|
-
import { forwardLogsTo, logger } from "
|
|
37
|
+
import { startMemoryReport, WORKER_MEMORY_SAMPLE_MS } from "../../storage/memory-report.js";
|
|
38
|
+
import { forwardLogsTo, logger } from "../../../utils/logger.js";
|
|
43
39
|
|
|
44
40
|
/**
|
|
45
41
|
* Everything this thread logs goes to the main thread, which owns the file.
|
|
@@ -70,8 +66,8 @@ forwardLogsTo((level, message) => {
|
|
|
70
66
|
// the hook above had a chance to register. Verified the hard way: with a static
|
|
71
67
|
// import the process still aborted, and the stack named the genuine polyfill.
|
|
72
68
|
const { TorrentPool, resolveDhtBootstrap } = await import("../torrent-pool.js");
|
|
73
|
-
const { collectStoreStats, findSharedStore, machineReserveBytes, pieceBufferCollection, reviseSpillBudgets, reviseStoreBudgets } =
|
|
74
|
-
await import("
|
|
69
|
+
const { SharedPieceStore, collectStoreStats, findSharedStore, machineReserveBytes, memoryClaim, pieceBufferCollection, reviseSpillBudgets, reviseStoreBudgets } =
|
|
70
|
+
await import("../../storage/piece-store/shared-piece-store.js");
|
|
75
71
|
|
|
76
72
|
// Resolved before the client exists, because the client builds its DHT in its
|
|
77
73
|
// own constructor and the addresses have to be in hand by then. Awaiting here
|
|
@@ -80,9 +76,9 @@ const { collectStoreStats, findSharedStore, machineReserveBytes, pieceBufferColl
|
|
|
80
76
|
const dhtBootstrap = await resolveDhtBootstrap();
|
|
81
77
|
|
|
82
78
|
const pool = new TorrentPool({
|
|
83
|
-
maxDiskBytes: workerData?.maxDiskBytes,
|
|
84
79
|
memoryBytes: workerData?.memoryBytes,
|
|
85
|
-
dhtBootstrap
|
|
80
|
+
dhtBootstrap,
|
|
81
|
+
pieceStore: SharedPieceStore
|
|
86
82
|
});
|
|
87
83
|
|
|
88
84
|
/** Torrents by sourceKey — the main thread names them, this thread owns them. */
|
|
@@ -406,13 +402,6 @@ async function runCommand(command, params, id) {
|
|
|
406
402
|
return { downloaded, uploaded };
|
|
407
403
|
}
|
|
408
404
|
|
|
409
|
-
case Command.SUBTITLE_TRACKS: {
|
|
410
|
-
const torrent = await requireTorrent(params.sourceKey);
|
|
411
|
-
return {
|
|
412
|
-
tracks: await subtitleTracksOf(torrent, params.fileIndex, params.sourceKey),
|
|
413
|
-
declared: await declaredSubtitleTracksOf(torrent, params.fileIndex, params.sourceKey)
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
405
|
|
|
417
406
|
case Command.FILL_FILE: {
|
|
418
407
|
const torrent = await requireTorrent(params.sourceKey);
|
|
@@ -421,58 +410,6 @@ async function runCommand(command, params, id) {
|
|
|
421
410
|
};
|
|
422
411
|
}
|
|
423
412
|
|
|
424
|
-
case Command.CONTAINER_TRACKS: {
|
|
425
|
-
const torrent = await requireTorrent(params.sourceKey);
|
|
426
|
-
return {
|
|
427
|
-
tracks: await containerTracksOf(torrent, params.fileIndex, params.sourceKey, {
|
|
428
|
-
// The header of a file nobody is playing has usually not arrived at
|
|
429
|
-
// all — a sidecar soundtrack is asked about before anyone has chosen
|
|
430
|
-
// it. Its head is a few hundred kilobytes, and without them there is
|
|
431
|
-
// nothing to read.
|
|
432
|
-
prefetchEdges: () =>
|
|
433
|
-
pool.prefetchFileEdges(torrent, params.fileIndex, {
|
|
434
|
-
headBytes: CONTAINER_HEAD_BYTES,
|
|
435
|
-
tailBytes: 0,
|
|
436
|
-
timeoutMs: 60_000
|
|
437
|
-
})
|
|
438
|
-
})
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
case Command.CONTAINER_MEDIA_INFO: {
|
|
443
|
-
const torrent = await requireTorrent(params.sourceKey);
|
|
444
|
-
return {
|
|
445
|
-
info: await containerMediaInfoOf(torrent, params.fileIndex, params.sourceKey, {
|
|
446
|
-
// Same reason as the track table above: the file this is asked about
|
|
447
|
-
// is often one nobody has played yet, so its head has to be fetched
|
|
448
|
-
// before there is anything to read.
|
|
449
|
-
prefetchEdges: () =>
|
|
450
|
-
pool.prefetchFileEdges(torrent, params.fileIndex, {
|
|
451
|
-
headBytes: CONTAINER_HEAD_BYTES,
|
|
452
|
-
tailBytes: 0,
|
|
453
|
-
timeoutMs: 60_000
|
|
454
|
-
})
|
|
455
|
-
})
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
case Command.CONTAINER_KEYFRAMES: {
|
|
460
|
-
const torrent = await requireTorrent(params.sourceKey);
|
|
461
|
-
return {
|
|
462
|
-
index: await containerKeyframesOf(torrent, params.fileIndex, params.sourceKey, {
|
|
463
|
-
// Both edges this time. A Matroska file's Cues sit at the END, behind
|
|
464
|
-
// a SeekHead in the head, so a read that has neither waits for the
|
|
465
|
-
// swarm twice over.
|
|
466
|
-
prefetchEdges: () =>
|
|
467
|
-
pool.prefetchFileEdges(torrent, params.fileIndex, {
|
|
468
|
-
headBytes: CONTAINER_HEAD_BYTES,
|
|
469
|
-
tailBytes: CONTAINER_HEAD_BYTES,
|
|
470
|
-
timeoutMs: 60_000
|
|
471
|
-
})
|
|
472
|
-
})
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
413
|
case Command.WARM_POSITION: {
|
|
477
414
|
const torrent = await requireTorrent(params.sourceKey);
|
|
478
415
|
return {
|
|
@@ -482,12 +419,9 @@ async function runCommand(command, params, id) {
|
|
|
482
419
|
params.sourceKey,
|
|
483
420
|
params.positionSeconds,
|
|
484
421
|
{
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
tailBytes: 0,
|
|
489
|
-
timeoutMs: 60_000
|
|
490
|
-
}),
|
|
422
|
+
// Told by the main thread, which is where what a file states about
|
|
423
|
+
// itself is read.
|
|
424
|
+
durationSeconds: params.durationSeconds,
|
|
491
425
|
fetchRegion: (start, bytes) =>
|
|
492
426
|
pool.prefetchFileRegion(torrent, params.fileIndex, start, bytes)
|
|
493
427
|
}
|
|
@@ -495,18 +429,6 @@ async function runCommand(command, params, id) {
|
|
|
495
429
|
};
|
|
496
430
|
}
|
|
497
431
|
|
|
498
|
-
case Command.SUBTITLE_CUES: {
|
|
499
|
-
const torrent = await requireTorrent(params.sourceKey);
|
|
500
|
-
const held = await cuesHeldFor(torrent, params.fileIndex, params.sourceKey, params.trackNumber);
|
|
501
|
-
return {
|
|
502
|
-
cues: held.cues,
|
|
503
|
-
coveredClusters: held.coveredClusters,
|
|
504
|
-
indexedClusters: held.indexedClusters,
|
|
505
|
-
codecId: held.track?.codecId ?? "",
|
|
506
|
-
codecPrivate: held.track?.codecPrivate ?? "",
|
|
507
|
-
language: held.track?.language ?? ""
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
432
|
|
|
511
433
|
case Command.FILE_STATS: {
|
|
512
434
|
const torrent = await requireTorrent(params.sourceKey);
|
|
@@ -577,6 +499,17 @@ async function runCommand(command, params, id) {
|
|
|
577
499
|
return true;
|
|
578
500
|
}
|
|
579
501
|
|
|
502
|
+
case Command.HELD_RANGES: {
|
|
503
|
+
const torrent = await requireTorrent(params.sourceKey);
|
|
504
|
+
return { ranges: heldRangesOf(torrent, params.fileIndex) };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
case Command.READ_HELD: {
|
|
508
|
+
const torrent = await requireTorrent(params.sourceKey);
|
|
509
|
+
const bytes = await readHeldBytes(torrent, params.fileIndex, params.start, params.end, logger);
|
|
510
|
+
return { bytes };
|
|
511
|
+
}
|
|
512
|
+
|
|
580
513
|
case Command.SPILL_ALLOWANCE: {
|
|
581
514
|
// The disk has one owner and it is on the main thread, where the segments
|
|
582
515
|
// are. What arrives is this thread’s whole share; the stores divide it
|
|
@@ -584,6 +517,21 @@ async function runCommand(command, params, id) {
|
|
|
584
517
|
return reviseSpillBudgets(Number(params.bytes));
|
|
585
518
|
}
|
|
586
519
|
|
|
520
|
+
case Command.MEMORY_ALLOWANCE: {
|
|
521
|
+
// Memory has one owner, on the main thread, and it is the same owner the
|
|
522
|
+
// disk has — the two resources trade, so they cannot be divided apart.
|
|
523
|
+
reviseStoreBudgets(Number(params.bytes));
|
|
524
|
+
return memoryClaim();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
case Command.WHOLE_FILES_ALLOWANCE: {
|
|
528
|
+
// Whole files are held on this thread and the disk has one owner, on the
|
|
529
|
+
// other. What arrives is their share; what goes back is what they hold
|
|
530
|
+
// after it, which is what the owner divides by next time.
|
|
531
|
+
const after = await completedFiles.allow(Number(params.bytes));
|
|
532
|
+
return after.bytes;
|
|
533
|
+
}
|
|
534
|
+
|
|
587
535
|
case Command.DESTROY_ALL: {
|
|
588
536
|
torrentsByKey.clear();
|
|
589
537
|
sourceRecipes.clear();
|
|
@@ -834,68 +782,30 @@ function describePieceBuffers() {
|
|
|
834
782
|
}
|
|
835
783
|
|
|
836
784
|
/**
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
785
|
+
* Say that pieces of the actively-read files of one torrent have arrived.
|
|
786
|
+
*
|
|
787
|
+
* It used to WALK them — reading each file's new subtitle clusters and pushing
|
|
788
|
+
* the cues it found. That put the cue reading, and with it a container parse,
|
|
789
|
+
* in the thread that owns the swarm; the walk is on the main thread now and
|
|
790
|
+
* this announces the one fact only this thread can know.
|
|
791
|
+
*
|
|
792
|
+
* Which files: the ones anything is stated for — a viewer's own picture and
|
|
793
|
+
* soundtrack through the priority map, and the ends of a file that is open. It
|
|
794
|
+
* replaced a count of readers, which said the same thing by keeping a second
|
|
795
|
+
* copy of it.
|
|
840
796
|
*
|
|
841
797
|
* @param {string} sourceKey
|
|
842
798
|
* @param {object} torrent
|
|
843
799
|
* @returns {void}
|
|
844
800
|
*/
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
// it.
|
|
850
|
-
for (const fileIndex of demandFor(torrent).register.files()) {
|
|
851
|
-
const key = `${sourceKey}:${fileIndex}`;
|
|
852
|
-
// A trigger that arrives while the previous pass is still walking is
|
|
853
|
-
// dropped, not queued. `verified` fires per piece, so on a fast download
|
|
854
|
-
// these arrive many times a second; the walk is serialized per file anyway,
|
|
855
|
-
// and a queue of identical passes would only postpone the one that has
|
|
856
|
-
// something new to find.
|
|
857
|
-
if (warmupInFlight.has(key)) {
|
|
858
|
-
continue;
|
|
859
|
-
}
|
|
860
|
-
warmupInFlight.add(key);
|
|
861
|
-
warmSubtitleCues(torrent, fileIndex, sourceKey)
|
|
862
|
-
.then((fresh) => {
|
|
863
|
-
for (const entry of fresh) {
|
|
864
|
-
const span = entry.spanStartSeconds === null
|
|
865
|
-
? "empty"
|
|
866
|
-
: `${entry.spanStartSeconds.toFixed(1)}-${entry.spanEndSeconds.toFixed(1)}s`;
|
|
867
|
-
log(
|
|
868
|
-
`subtitle push ${sourceKey.slice(0, 8)}:${fileIndex} track ${entry.trackIndex}: ` +
|
|
869
|
-
`${entry.cues.length} new cue(s) covering ${span}, ` +
|
|
870
|
-
`clusters walked ${entry.walkedClusters}/${entry.indexedClusters}, cursor ${entry.cursor}, ` +
|
|
871
|
-
"posting to main thread"
|
|
872
|
-
);
|
|
873
|
-
parentPort.postMessage({
|
|
874
|
-
type: Event.SUBTITLE_CUES_READY,
|
|
875
|
-
sourceKey,
|
|
876
|
-
fileIndex,
|
|
877
|
-
trackIndex: entry.trackIndex,
|
|
878
|
-
cues: entry.cues,
|
|
879
|
-
language: entry.language,
|
|
880
|
-
cursor: entry.cursor
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
})
|
|
884
|
-
.catch((error) => {
|
|
885
|
-
log(`subtitle warmup ${sourceKey}:${fileIndex} failed: ${error instanceof Error ? error.message : error}`);
|
|
886
|
-
})
|
|
887
|
-
.finally(() => {
|
|
888
|
-
warmupInFlight.delete(key);
|
|
889
|
-
});
|
|
801
|
+
function announceArrivals(sourceKey, torrent) {
|
|
802
|
+
const fileIndexes = [...demandFor(torrent).register.files()];
|
|
803
|
+
if (fileIndexes.length === 0) {
|
|
804
|
+
return;
|
|
890
805
|
}
|
|
806
|
+
parentPort.postMessage({ type: Event.PIECES_ARRIVED, sourceKey, fileIndexes });
|
|
891
807
|
}
|
|
892
808
|
|
|
893
|
-
/**
|
|
894
|
-
* Files whose warmup pass has not finished yet, by `sourceKey:fileIndex`.
|
|
895
|
-
*
|
|
896
|
-
* @type {Set<string>}
|
|
897
|
-
*/
|
|
898
|
-
const warmupInFlight = new Set();
|
|
899
809
|
|
|
900
810
|
/**
|
|
901
811
|
* Torrents already wired to warm their subtitle cues the moment a piece
|
|
@@ -903,7 +813,7 @@ const warmupInFlight = new Set();
|
|
|
903
813
|
*
|
|
904
814
|
* @type {WeakSet<object>}
|
|
905
815
|
*/
|
|
906
|
-
const
|
|
816
|
+
const arrivalsWired = new WeakSet();
|
|
907
817
|
|
|
908
818
|
/**
|
|
909
819
|
* A piece becoming readable is the actual event a cue can be pulled from —
|
|
@@ -918,29 +828,28 @@ const subtitleWarmupWired = new WeakSet();
|
|
|
918
828
|
* @param {object} torrent
|
|
919
829
|
* @returns {void}
|
|
920
830
|
*/
|
|
921
|
-
function
|
|
922
|
-
if (
|
|
831
|
+
function ensureArrivalsWired(sourceKey, torrent) {
|
|
832
|
+
if (arrivalsWired.has(torrent)) {
|
|
923
833
|
return;
|
|
924
834
|
}
|
|
925
|
-
|
|
926
|
-
torrent.on("verified", () =>
|
|
835
|
+
arrivalsWired.add(torrent);
|
|
836
|
+
torrent.on("verified", () => announceArrivals(sourceKey, torrent));
|
|
927
837
|
}
|
|
928
838
|
|
|
929
839
|
/**
|
|
930
|
-
* How often
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
934
|
-
* skips clusters it has already read).
|
|
840
|
+
* How often arrivals are announced regardless, as a fallback beside the
|
|
841
|
+
* per-piece `verified` listener above — it catches a listener attached after
|
|
842
|
+
* pieces had already verified, and anything the event path might otherwise
|
|
843
|
+
* miss. Cheap: the walk it wakes skips clusters it has already read.
|
|
935
844
|
*/
|
|
936
|
-
const
|
|
845
|
+
const ARRIVAL_ANNOUNCE_INTERVAL_MS = 3_000;
|
|
937
846
|
|
|
938
847
|
setInterval(() => {
|
|
939
848
|
for (const [sourceKey, torrent] of pool.torrents) {
|
|
940
|
-
|
|
941
|
-
|
|
849
|
+
ensureArrivalsWired(sourceKey, torrent);
|
|
850
|
+
announceArrivals(sourceKey, torrent);
|
|
942
851
|
}
|
|
943
|
-
},
|
|
852
|
+
}, ARRIVAL_ANNOUNCE_INTERVAL_MS).unref();
|
|
944
853
|
|
|
945
854
|
/**
|
|
946
855
|
* Files this proxy has downloaded whole. One directory, two readers of it: this
|